Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 4-fix-card10-l0dable-on-crates-io
  • 5-implement-client-c-in-plain-rust
  • api
  • build-from-root
  • master
  • no-str_to_cstr
  • card10-alloc-0.1.0
  • card10-alloc-0.1.1
  • card10-l0dable-0.2.0
  • card10-l0dable-0.3.0
  • card10-sys-1.10.0
  • card10-sys-1.9.0
12 results

Target

Select target project
No results found
Select Git revision
  • 4-fix-card10-l0dable-on-crates-io
  • 5-implement-client-c-in-plain-rust
  • build-from-root
  • master
4 results
Show changes
68 files
+ 18779
675
Compare changes
  • Side-by-side
  • Inline

Files

.bin/cargo-card10

deleted100755 → 0
+0 −10
Original line number Diff line number Diff line
#!/usr/bin/env bash

set -e

cargo build --release
arm-none-eabi-objcopy -O binary target/thumbv7em-none-eabi/release/$2 card10.bin
python c/bootloader/crc_patch.py card10.bin

echo "Copy this file to the device in USB Mass Storage mode:"
ls -l card10.bin
+5 −0
Original line number Diff line number Diff line
target/
venv/
.idea/

# nix results
result*
+33 −9
Original line number Diff line number Diff line
nix:
l0dables:
  stage: build
  image: nixos/nix:latest
  variables:
@@ -6,4 +6,28 @@ nix:
  script:
    - nix-channel --add https://nixos.org/channels/nixos-19.03 nixpkgs
    - nix-channel --update
        - nix-build
    - nix-build -A l0dables
    - cp -r result/apps apps
  artifacts:
    paths:
     - apps
    expire_in: 1 week

firmware:
  stage: build
  image: nixos/nix:latest
  variables:
    GIT_SUBMODULE_STRATEGY: recursive
  script:
    - nix-channel --add https://nixos.org/channels/nixos-19.03 nixpkgs
    - nix-channel --update
    - nix-build release.nix
    - cp -r result/* .
  artifacts:
    paths:
      - card10.bin
      - apps
      - main.py
      - menu.py
      - card10.cfg
    expire_in: 1 week
+2 −2
Original line number Diff line number Diff line
[submodule "c"]
	path = c
[submodule "card10-sys/firmware"]
	path = card10-sys/firmware
	url = https://git.card10.badge.events.ccc.de/card10/firmware.git
Original line number Diff line number Diff line
From baef13b1adb6f7bf01a7dfd7f4f44a5a3e950b1d Mon Sep 17 00:00:00 2001
From: Astro <astro@spaceboyz.net>
Date: Thu, 5 Sep 2019 02:22:45 +0200
Subject: [PATCH] hack(epicardium): reduce init delay from 2s to 0.1s

---
 epicardium/main.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/epicardium/main.c b/epicardium/main.c
index 51edbe93..7607d39d 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -32,7 +32,7 @@ int main(void)
 	epic_disp_print(10, 20, "Epicardium", 0xfe20, 0x0000);
 	epic_disp_print(off > 0 ? off : 0, 40, version_buf, 0xfe20, 0x0000);
 	epic_disp_update();
-	mxc_delay(2000000);
+	mxc_delay(100000);
 
 	LOG_DEBUG("startup", "Initializing tasks ...");
 
-- 
2.22.1
+149 −122

File changed.

Preview size limit exceeded, changes collapsed.

+9 −1
Original line number Diff line number Diff line
[workspace]
members = [
  "l0dable",
  "card10-sys",
  "card10-alloc",
  "card10-l0dable",
  "example",
  "rkanoid",
  "draw-image",
]

default-members = [
  "example",
  "rkanoid",
  "draw-image",
]

[profile.release]
@@ -16,3 +21,6 @@ debug = true
opt-level = "s"
lto = true
panic = "abort"

[patch.crates-io]
alloc-cortex-m = { git = 'https://github.com/rust-embedded/alloc-cortex-m' }
+130 −29
Original line number Diff line number Diff line
# Rust support for the card10 CCCamp15 badge
# Rust support for the card10 CCCamp19 badge

## Prerequisites
## Prepare your card10

**Jailbreaking is no longer necessary!**

Starting with firmware v1.9, running ELF binaries requires a
`/card10.cfg` with the following content:

You need Rust Nightly and the arm-none-eabi-gcc toolchain, including libc.
```
execute_elf=true
```

### Arch Linux
## Prebuilt binaries

    sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib
By courtesy of this Gitlab's CI system, and NixOS, we build `.elf`
files for you drop into the `apps/` directory of your card10 badge.

## Usage
For each commit in this repository, we also build complete firmware
images with the required configuration and our example binaries.

You need Rust nightly. Use rustup or NixOS.
https://git.card10.badge.events.ccc.de/astro/rust-card10/-/jobs

## Prerequisites

You need rust nightly and a working setup to compile the card10
firmware including the matching libc.

1) For instructions how to setup rust please see https://rustup.rs.

   Please ensure that you installed the latest rust nightly toolchain
   and add the `thumbv7em-none-eabi` target.

   ```shell
rustup update nightly
rustup override set nightly
rustup target add thumbv7em-none-eabi
   rustup toolchain install nightly
   rustup update
   rustup target add thumbv7em-none-eabi --toolchain nightly
   ```
When cloning use `--recursive` to get the submodules, otherwise update them
afterwards:

2) For instructions how to setup the card10 firmware check the dependency
   chapter in https://firmware.card10.badge.events.ccc.de/how-to-build.html.

3) Additionally you may need the packages for the llvm and libc i386
   dev headers.

4) Clone this repository with `--recursive` to get the submodules,
   otherwise update them afterwards:

   ```shell
   git submodule update --init --recursive
   ```

Check out this repo's submodule (the C firmware).
## Build and run Rust loadables

### Setup

If you want to come up with your own rust based loadable crate a few
preparations are required:

  - Setup the new crate repository.
 
  - Add `card10-l0dable = "^0.1"` as a dependency to your new crate.
 
  - Change the configuration of the default cargo release profile inside your
    `Cargo.toml` file:
 
    ```
    [profile.release]
    opt-level = "s"
    panic = "abort"
    ```

  - Create (or update) the `thumbv7em-none-eabi` target configuration at
    `$PROJECT/.cargo/config` with the following rustflags:
 
    ```
    [target.thumbv7em-none-eabi]
    rustflags = [
      "-C", "linker=arm-none-eabi-gcc",
      "-C", "link-args=-Tl0dable.ld -n -pie -fPIC",
      "-C", "relocation-model=pic",
    ]

    [build]
    target = "thumbv7em-none-eabi"
    ```

  - Ensure that your crate is marked as a `non_std` project and make
    `card10-l0dable` aware of your custom main function. This should require
    the following update to your `main.rs` file.

    ```main.rs
    #![no_std]
    #![no_main]

    use card10_l0dable::main;

    main!(main);
    fn main() {}
    ```

### Compilation

To compile the project use the nightly toolchain and define the proper target.

```shell
cd example
cargo build --release
cargo +nightly build --release --target thumbv7em-none-eabi
```

Then copy the resulting
`../target/thumbv7em-none-eabi/release/l0dable-example` to the badge
in USB Mass Storage mode in the `/apps/` subfolder. Don't forget to
rename with the `.elf` extension!
### Transfer to card10

Then copy the resulting executable from the target directory 
`target/thumbv7em-none-eabi/release/example` into the
`apps` directory of your badge.

**Attention**: Its necessary to rename the executable to add the `elf`
extension (e.g `example` must be renamed as `example.elf`).

## Crates

| Crate    | Description                                               |
| ----     | ---                                                       |
| example  | l0dable example                                           |
| l0dable  | Helpers for building l0dables                             |
| Crate          | Documentation             | Description                                      |
| ----           | ---                       | ---                                              |
| card10-sys     | [docs.rs][card10-sys]     | Unsafe C bindings for l0dables                   |
| card10-alloc   | [docs.rs][card10-alloc]   | alloc::* support for l0dables                    |
| card10-l0dable | [docs.rs][card10-l0dable] | High-level crate for building l0dables           |
| example        |                           | l0dable example                                  |
| rkanoid        |                           | Arkanoid clone                                   |
| draw-image     |                           | Example of drawing a static image to the display |

[card10-sys]: https://docs.rs/card10-sys/
[card10-alloc]: https://docs.rs/card10-alloc/
[card10-l0dable]: https://docs.rs/card10-l0dable/


## Misc

### How to update the firmware bindings

## TODO
1) Update the `card10-sys/firmware` submodule to the latest firmware state.

2) Rebuild the firmware as described above.

3) Run the following script from the project root directory

   ```shell
   python card10-sys/firmware/epicardium/api/genapi.py -H card10-sys/firmware/epicardium/epicardium.h -c card10-sys/vendor/client.c -s card10-sys/vendor/server.c
   ```

- [ ] alloc
4) Rebuild your app :)

c @ ea85141f

Original line number Diff line number Diff line
Subproject commit ea85141fe8993084882ffb945112e6891318f153
+25 −0
Original line number Diff line number Diff line
[package]

name = "card10-alloc"
version = "0.1.1"
authors = ["Astro <astro@spaceboyz.net>"]
license = "MIT/Apache-2.0"
edition = "2018"

description = "Dynamic memory allocation for card10 l0dables"
repository = "https://git.card10.badge.events.ccc.de/astro/rust-card10"
homepage = "https://git.card10.badge.events.ccc.de/astro/rust-card10"
documentation = "https://docs.rs/card10-alloc"

categories = ["no-std", "memory-management"]
keywords = [
    "CCC",
    "CCCamp2019",
    "CCCamp19",
    "card10",
    "l0dable",
]

[dependencies]
alloc-cortex-m = "0.3"
card10-sys = { path = "../card10-sys" }
+66 −0
Original line number Diff line number Diff line
//! Support for dynamically allocated memory
//!
//! Reproduces l0dable hardware.c's `_sbrk()`
//!
//! Unfortunately, we cannot link `_sbrk()` directly because it
//! references the unwieldy `errno`.
//!
//! ## Example
//!
//! ```rust
//! #![no_std]
//! #![no_main]
//!
//! extern crate alloc;
//! use alloc::vec;
//! use card10_l0dable::*;
//!
//! main!(main);
//! fn main() {
//!     // Pass stack headroom
//!     card10_alloc::init(128 * 1024);
//!     let mut xs = vec![];
//!     xs.push(23);
//! }
//! ```
#![no_std]
#![feature(asm)]
#![feature(alloc_error_handler)]

use core::alloc::Layout;
use alloc_cortex_m::CortexMHeap;
use card10_sys as _;

#[global_allocator]
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();

extern "C" {
    static mut __heap_start: u32;
}

#[inline(always)]
fn sp() -> usize {
    let mut value;
    unsafe {
        asm!("mov $0, sp" : "=r" (value) ::: "volatile");
    }
    value
}

/// Call this before using anything from `alloc`.
///
/// Consider the size of your stack-allocated variables for the
/// `stack_headroom` parameter.
///
/// Returns heap size
pub fn init(stack_headroom: usize) -> usize {
    let start = unsafe { &__heap_start } as *const _ as usize;
    let size = sp() - stack_headroom - start;
    unsafe { ALLOCATOR.init(start, size); }
    size
}

#[alloc_error_handler]
fn on_oom(_layout: Layout) -> ! {
    panic!("OOM")
}
+32 −0
Original line number Diff line number Diff line
[package]
edition = "2018"
name = "card10-l0dable"
version = "0.3.0"
authors = [
    "Astro <astro@spaceboyz.net>",
    "Kloenk <me@kloenk.de>",
    "Bruno Kirschner <bruno.kirschner@online.de>",
    "Puzzlewolf <camp@nora.pink>",
    "Raphael Nestler <raphael.nestler@gmail.com>",
    "Danilo Bargen <mail@dbrgn.ch>",
    "toon <toon@c3d2.de>",
    "Sergey Pepyakin <sergei@parity.io>",

]
license = "MIT/Apache-2.0"
repository = "https://git.card10.badge.events.ccc.de/astro/rust-card10"
homepage = "https://git.card10.badge.events.ccc.de/astro/rust-card10"
keywords = [
    "CCC",
    "CCCamp2019",
    "CCCamp19",
    "card10",
    "l0dable",
]
categories = ["no-std"]
description = "make l0dables for the Card10 (CCCamp 2019) badge"
documentation = "https://docs.rs/card10-l0dable"

[dependencies]
card10-sys = { path = "../card10-sys" }
embedded-graphics = "0.5"
+201 −0
Original line number Diff line number Diff line
                              Apache License
                        Version 2.0, January 2004
                     http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

   "License" shall mean the terms and conditions for use, reproduction,
   and distribution as defined by Sections 1 through 9 of this document.

   "Licensor" shall mean the copyright owner or entity authorized by
   the copyright owner that is granting the License.

   "Legal Entity" shall mean the union of the acting entity and all
   other entities that control, are controlled by, or are under common
   control with that entity. For the purposes of this definition,
   "control" means (i) the power, direct or indirect, to cause the
   direction or management of such entity, whether by contract or
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
   outstanding shares, or (iii) beneficial ownership of such entity.

   "You" (or "Your") shall mean an individual or Legal Entity
   exercising permissions granted by this License.

   "Source" form shall mean the preferred form for making modifications,
   including but not limited to software source code, documentation
   source, and configuration files.

   "Object" form shall mean any form resulting from mechanical
   transformation or translation of a Source form, including but
   not limited to compiled object code, generated documentation,
   and conversions to other media types.

   "Work" shall mean the work of authorship, whether in Source or
   Object form, made available under the License, as indicated by a
   copyright notice that is included in or attached to the work
   (an example is provided in the Appendix below).

   "Derivative Works" shall mean any work, whether in Source or Object
   form, that is based on (or derived from) the Work and for which the
   editorial revisions, annotations, elaborations, or other modifications
   represent, as a whole, an original work of authorship. For the purposes
   of this License, Derivative Works shall not include works that remain
   separable from, or merely link (or bind by name) to the interfaces of,
   the Work and Derivative Works thereof.

   "Contribution" shall mean any work of authorship, including
   the original version of the Work and any modifications or additions
   to that Work or Derivative Works thereof, that is intentionally
   submitted to Licensor for inclusion in the Work by the copyright owner
   or by an individual or Legal Entity authorized to submit on behalf of
   the copyright owner. For the purposes of this definition, "submitted"
   means any form of electronic, verbal, or written communication sent
   to the Licensor or its representatives, including but not limited to
   communication on electronic mailing lists, source code control systems,
   and issue tracking systems that are managed by, or on behalf of, the
   Licensor for the purpose of discussing and improving the Work, but
   excluding communication that is conspicuously marked or otherwise
   designated in writing by the copyright owner as "Not a Contribution."

   "Contributor" shall mean Licensor and any individual or Legal Entity
   on behalf of whom a Contribution has been received by Licensor and
   subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   copyright license to reproduce, prepare Derivative Works of,
   publicly display, publicly perform, sublicense, and distribute the
   Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   (except as stated in this section) patent license to make, have made,
   use, offer to sell, sell, import, and otherwise transfer the Work,
   where such license applies only to those patent claims licensable
   by such Contributor that are necessarily infringed by their
   Contribution(s) alone or by combination of their Contribution(s)
   with the Work to which such Contribution(s) was submitted. If You
   institute patent litigation against any entity (including a
   cross-claim or counterclaim in a lawsuit) alleging that the Work
   or a Contribution incorporated within the Work constitutes direct
   or contributory patent infringement, then any patent licenses
   granted to You under this License for that Work shall terminate
   as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
   Work or Derivative Works thereof in any medium, with or without
   modifications, and in Source or Object form, provided that You
   meet the following conditions:

   (a) You must give any other recipients of the Work or
       Derivative Works a copy of this License; and

   (b) You must cause any modified files to carry prominent notices
       stating that You changed the files; and

   (c) You must retain, in the Source form of any Derivative Works
       that You distribute, all copyright, patent, trademark, and
       attribution notices from the Source form of the Work,
       excluding those notices that do not pertain to any part of
       the Derivative Works; and

   (d) If the Work includes a "NOTICE" text file as part of its
       distribution, then any Derivative Works that You distribute must
       include a readable copy of the attribution notices contained
       within such NOTICE file, excluding those notices that do not
       pertain to any part of the Derivative Works, in at least one
       of the following places: within a NOTICE text file distributed
       as part of the Derivative Works; within the Source form or
       documentation, if provided along with the Derivative Works; or,
       within a display generated by the Derivative Works, if and
       wherever such third-party notices normally appear. The contents
       of the NOTICE file are for informational purposes only and
       do not modify the License. You may add Your own attribution
       notices within Derivative Works that You distribute, alongside
       or as an addendum to the NOTICE text from the Work, provided
       that such additional attribution notices cannot be construed
       as modifying the License.

   You may add Your own copyright statement to Your modifications and
   may provide additional or different license terms and conditions
   for use, reproduction, or distribution of Your modifications, or
   for any such Derivative Works as a whole, provided Your use,
   reproduction, and distribution of the Work otherwise complies with
   the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
   any Contribution intentionally submitted for inclusion in the Work
   by You to the Licensor shall be under the terms and conditions of
   this License, without any additional terms or conditions.
   Notwithstanding the above, nothing herein shall supersede or modify
   the terms of any separate license agreement you may have executed
   with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
   names, trademarks, service marks, or product names of the Licensor,
   except as required for reasonable and customary use in describing the
   origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
   agreed to in writing, Licensor provides the Work (and each
   Contributor provides its Contributions) on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   implied, including, without limitation, any warranties or conditions
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
   PARTICULAR PURPOSE. You are solely responsible for determining the
   appropriateness of using or redistributing the Work and assume any
   risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
   whether in tort (including negligence), contract, or otherwise,
   unless required by applicable law (such as deliberate and grossly
   negligent acts) or agreed to in writing, shall any Contributor be
   liable to You for damages, including any direct, indirect, special,
   incidental, or consequential damages of any character arising as a
   result of this License or out of the use or inability to use the
   Work (including but not limited to damages for loss of goodwill,
   work stoppage, computer failure or malfunction, or any and all
   other commercial damages or losses), even if such Contributor
   has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
   the Work or Derivative Works thereof, You may choose to offer,
   and charge a fee for, acceptance of support, warranty, indemnity,
   or other liability obligations and/or rights consistent with this
   License. However, in accepting such obligations, You may act only
   on Your own behalf and on Your sole responsibility, not on behalf
   of any other Contributor, and only if You agree to indemnify,
   defend, and hold each Contributor harmless for any liability
   incurred by, or claims asserted against, such Contributor by reason
   of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

   To apply the Apache License to your work, attach the following
   boilerplate notice, with the fields enclosed by brackets "[]"
   replaced with your own identifying information. (Don't include
   the brackets!)  The text should be enclosed in the appropriate
   comment syntax for the file format. We also recommend that a
   file or class name and description of purpose be included on the
   same "printed page" as the copyright notice for easier
   identification within third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25 −0
Original line number Diff line number Diff line
Copyright (c) 2014 The Rust Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Original line number Diff line number Diff line
use core::mem::uninitialized;
use core::marker::PhantomData;
use super::bindings::*;
/// Accelerometer, Gyroscope, Orientation

use core::fmt::Write;
use core::{
    fmt::{self, Display, Write},
    marker::PhantomData,
    mem::MaybeUninit,
};

use card10_sys::*;

pub trait SensorType {
    /// sensor_type in C, sensor_id in Python
@@ -10,6 +14,7 @@ pub trait SensorType {
    fn convert_single(value: i16) -> f32;
}

#[derive(Debug)]
pub struct Accelerometer;
impl SensorType for Accelerometer {
    fn sensor_type() -> u32 {
@@ -20,6 +25,7 @@ impl SensorType for Accelerometer {
    }
}

#[derive(Debug)]
pub struct Gyroscope;
impl SensorType for Gyroscope {
    fn sensor_type() -> u32 {
@@ -30,6 +36,7 @@ impl SensorType for Gyroscope {
    }
}

#[derive(Debug)]
pub struct Orientation;
impl SensorType for Orientation {
    fn sensor_type() -> u32 {
@@ -48,43 +55,68 @@ pub struct Sensor<S: SensorType> {
}

impl<S: SensorType> Sensor<S> {
    pub fn start() -> Self {
    /// Use one of:
    /// - `BHI160::<Accelerometer>::start()`
    /// - `BHI160::<Gyroscope>::start()`
    /// - `BHI160::<Orientation>::start()`
    fn new(stream_id: i32) -> Self {
        Self {
            stream_id,
            _kind: PhantomData,
        }
    }

    pub fn start() -> Result<Self, Error> {
        let mut cfg = bhi160_sensor_config {
            sample_buffer_len: 200,
            sample_rate: 4,
            dynamic_range: 2,
            _padding: [0u8; 8],
        };
        let stream_id = unsafe {
            epic_bhi160_enable_sensor(S::sensor_type(), &mut cfg)

        let stream_id = unsafe { epic_bhi160_enable_sensor(S::sensor_type(), &mut cfg) };
        if stream_id < 0 {
            let error = match -stream_id {
                errno::EBUSY => Error::DriverBusy,
                _ => Error::Unknown(stream_id),
            };
        Sensor {
            stream_id,
            _kind: PhantomData,

            return Err(error);
        }

        Ok(Sensor::new(stream_id))
    }

    pub fn read(&self) -> SensorData<S> {
        let mut buf: [bhi160_data_vector; DATA_MAX] = unsafe {
            uninitialized()
        };
        let n = unsafe {
            epic_stream_read(self.stream_id, buf.as_mut_ptr() as *mut _, buf.len())
    pub fn read(&self) -> Result<SensorData<S>, Error> {
        let mut buffer = MaybeUninit::<[bhi160_data_vector; DATA_MAX]>::zeroed();
        let buffer_pointer = buffer.as_mut_ptr() as *mut _;

        let packet_count = unsafe { epic_stream_read(self.stream_id, buffer_pointer, DATA_MAX) };
        if packet_count < 0 {
            let error = match -packet_count {
                errno::ENODEV => Error::SensorUnavailable,
                errno::EBADF => Error::SensorDescriptorUnknown,
                errno::EINVAL => Error::InvalidSampleCount,
                errno::EBUSY => Error::CouldNotAcquireLock,
                _ => Error::Unknown(packet_count),
            };
        if n < 0 {
            panic!("epic_stream_read fail");

            return Err(error);
        }
        let n = n as usize;
        SensorData {
            buf, n,

        Ok(SensorData {
            buf: unsafe { buffer.assume_init() },
            n: packet_count as usize,
            _kind: PhantomData,
        }
        })
    }
}

impl<S: SensorType> Drop for Sensor<S> {
    fn drop(&mut self) {
        unsafe { epic_bhi160_disable_sensor(S::sensor_type()); }
        unsafe {
            epic_bhi160_disable_sensor(S::sensor_type());
        }
    }
}

@@ -95,13 +127,11 @@ pub struct SensorData<S> {
}

impl<'a, S: SensorType> IntoIterator for &'a SensorData<S> {
    type Item = SensorDataItem;
    type Item = SensorDataItem<S>;
    type IntoIter = SensorDataIter<'a, S>;

    fn into_iter(self) -> Self::IntoIter {
        SensorDataIter {
            data: self,
            pos: 0,
        }
        SensorDataIter { data: self, pos: 0 }
    }
}

@@ -111,33 +141,54 @@ pub struct SensorDataIter<'a, S> {
}

impl<'a, S: SensorType> Iterator for SensorDataIter<'a, S> {
    type Item = SensorDataItem;
    type Item = SensorDataItem<S>;

    fn next(&mut self) -> Option<Self::Item> {
        while self.pos < self.data.n {
            let vec = &self.data.buf[self.pos];
            self.pos += 1;
            if vec.data_type == DATA_TYPE_VECTOR {
                let item = SensorDataItem {
                    x: S::convert_single(vec.x),
                    y: S::convert_single(vec.y),
                    z: S::convert_single(vec.z),
            if vec.data_type != DATA_TYPE_VECTOR {
                writeln!(crate::UART, "Sensor: skip type {}\r", vec.data_type).ok();
                continue;
            }

            let item = SensorDataItem::<S> {
                x_raw: vec.x,
                y_raw: vec.y,
                z_raw: vec.z,
                status: vec.status,
                _kind: PhantomData,
            };

            self.pos += 1;

            return Some(item);
            } else {
                writeln!(crate::UART, "Sensor: skip type {}\r", vec.data_type).unwrap();
            }
        }

        None
    }
}

#[derive(Debug, Clone)]
pub struct SensorDataItem {
    x: f32,
    y: f32,
    z: f32,
    status: u8,
pub struct SensorDataItem<S: SensorType> {
    pub x_raw: i16,
    pub y_raw: i16,
    pub z_raw: i16,
    pub status: u8,
    _kind: PhantomData<S>,
}

impl<S: SensorType> SensorDataItem<S> {
    pub fn get_x(&self) -> f32 {
        S::convert_single(self.x_raw)
    }

    pub fn get_y(&self) -> f32 {
        S::convert_single(self.y_raw)
    }

    pub fn get_z(&self) -> f32 {
        S::convert_single(self.z_raw)
    }
}

#[repr(C)]
@@ -152,3 +203,38 @@ pub struct bhi160_data_vector {
}

const DATA_TYPE_VECTOR: u8 = 0;

// -----------------------------------------------------------------------------
// BHI160 Error
// -----------------------------------------------------------------------------

#[derive(Debug)]
pub enum Error {
    /// The descriptor table lock could not be acquired.
    CouldNotAcquireLock,
    /// The BHI160 driver is currently busy with other tasks and could not be
    /// acquired for enabling a sensor.
    DriverBusy,
    /// The requested sample `count` is not a multiple of the sensor's sample
    /// size.
    InvalidSampleCount,
    /// The given sensor descriptor is unknown.
    SensorDescriptorUnknown,
    /// Sensor is not currently available.
    SensorUnavailable,
    /// Not yet documented and therefore unknown error types.
    Unknown(i32),
}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::CouldNotAcquireLock => writeln!(f, "Could not acquire BHI160 lock."),
            Error::DriverBusy => writeln!(f, "The BHI160 Driver is busy."),
            Error::InvalidSampleCount => writeln!(f, "Sample couldn't invalid."),
            Error::SensorDescriptorUnknown => writeln!(f, "Unknown BHI160 sensor descriptor."),
            Error::SensorUnavailable => writeln!(f, "The BHI160 sensor is currently unavailable."),
            Error::Unknown(id) => writeln!(f, "Unknown error id: {}", id),
        }
    }
}
Original line number Diff line number Diff line
/// Enviromental sensor

use card10_sys::*;
use core::mem::uninitialized;
use super::bindings::*;

pub struct BME680;
pub type SensorData = bme680_sensor_data;
@@ -7,7 +9,7 @@ pub type SensorData = bme680_sensor_data;
impl BME680 {
    pub fn start() -> Self {
        if unsafe { epic_bme680_init() } != 0 {
            panic!("Cannot start light sensor");
            panic!("Cannot start BME680 sensor");
        }
        BME680
    }
@@ -24,6 +26,8 @@ impl BME680 {

impl Drop for BME680 {
    fn drop(&mut self) {
        unsafe { epic_bme680_deinit(); }
        unsafe {
            epic_bme680_deinit();
        }
    }
}
Original line number Diff line number Diff line
use super::bindings::*;
use card10_sys::*;

/// Button inputs
pub struct Buttons {
    state: u32,
}

impl Buttons {
    /// Read the current button state
    pub fn read() -> Self {
        let mask =
            epic_button_BUTTON_LEFT_BOTTOM |
            epic_button_BUTTON_RIGHT_BOTTOM |
            epic_button_BUTTON_LEFT_TOP |
            epic_button_BUTTON_RIGHT_TOP |
            epic_button_BUTTON_RESET;
        let mask = epic_button_BUTTON_LEFT_BOTTOM
            | epic_button_BUTTON_RIGHT_BOTTOM
            | epic_button_BUTTON_LEFT_TOP
            | epic_button_BUTTON_RIGHT_TOP
            | epic_button_BUTTON_RESET;
        let state = unsafe { epic_buttons_read(mask as u8) }.into();
        Buttons { state }
    }
Original line number Diff line number Diff line
use super::bindings::*;
use super::framebuffer::FrameBuffer;
use card10_sys::*;

#[derive(Debug, Clone, Copy)]
#[repr(C)]
@@ -31,9 +32,7 @@ impl Color {

    pub fn rgb8(r8: u8, g8: u8, b8: u8) -> Self {
        let c =
            ((u16::from(r8) & 0xF8) << 8) |
            ((u16::from(g8) & 0xFA) << 3) |
            (u16::from(b8) & 0xF8);
            ((u16::from(r8) & 0xF8) << 8) | ((u16::from(g8) & 0xFA) << 3) | (u16::from(b8) & 0xF8);
        Color(c)
    }

@@ -50,7 +49,7 @@ impl Color {
    }

    pub fn g8(&self) -> u8 {
        self.r() << 2
        self.g() << 2
    }

    pub fn b(&self) -> u8 {
@@ -58,7 +57,7 @@ impl Color {
    }

    pub fn b8(&self) -> u8 {
        self.r() << 3
        self.b() << 3
    }
}

@@ -76,59 +75,167 @@ pub enum FillStyle {

pub struct Display;

#[repr(u8)]
pub enum Font {
    Font8 = disp_font_name_DISP_FONT8 as u8,
    Font12 = disp_font_name_DISP_FONT12 as u8,
    Font16 = disp_font_name_DISP_FONT16 as u8,
    Font20 = disp_font_name_DISP_FONT20 as u8,
    Font24 = disp_font_name_DISP_FONT24 as u8,
}

/// Immediate mode routines
impl Display {
    pub const W: u16 = 160;
    pub const H: u16 = 80;
    pub const FONT_W: u16 = 14;
    pub const FONT_H: u16 = 20;
    pub const W: i16 = 160;
    pub const H: i16 = 80;
    pub const FONT_W: i16 = 14;
    pub const FONT_H: i16 = 20;

    /// Open the display, return an instance
    pub fn open() -> Self {
        unsafe { epic_disp_open(); }
        unsafe {
            epic_disp_open();
        }
        Display
    }

    /// Write Epicardium's framebuffer to the display
    pub fn update(&self) {
        unsafe { epic_disp_update(); }
        unsafe {
            epic_disp_update();
        }
    }

    /// Clear everything with a solid `color`
    pub fn clear(&self, color: Color) {
        unsafe { epic_disp_clear(color.0); }
        unsafe {
            epic_disp_clear(color.0);
        }
    }

    /// Print text
    ///
    /// s must be 0-terminated
    pub fn print(&self, x: u16, y: u16, s: &[u8], fg: Color, bg: Color) {
    pub fn print(&self, x: i16, y: i16, s: &[u8], fg: Color, bg: Color) {
        unsafe {
            epic_disp_print(x, y, s.as_ptr(), fg.0, bg.0);
        }
    }

    pub fn pixel(&self, x: u16, y: u16, color: Color) {
    /// Print text with a selected font
    ///
    /// s must be 0-terminated
    pub fn print_adv(&self, font: Font, x: i16, y: i16, s: &[u8], fg: Color, bg: Color) {
        unsafe {
            epic_disp_print_adv(font as u8, x, y, s.as_ptr(), fg.0, bg.0);
        }
    }

    /// Set a pixel
    pub fn pixel(&self, x: i16, y: i16, color: Color) {
        unsafe {
            epic_disp_pixel(x, y, color.0);
        }
    }

    pub fn line(&self, x1: u16, y1: u16, x2: u16, y2: u16, color: Color, linestyle: LineStyle, pixelsize: u16) {
    /// Draw a line
    pub fn line(
        &self,
        x1: i16,
        y1: i16,
        x2: i16,
        y2: i16,
        color: Color,
        linestyle: LineStyle,
        pixelsize: u16,
    ) {
        unsafe {
            epic_disp_line(x1, y1, x2, y2, color.0, linestyle as u32, pixelsize);
        }
    }

    pub fn rect(&self, x1: u16, y1: u16, x2: u16, y2: u16, color: Color, fillstyle: FillStyle, pixelsize: u16) {
    pub fn rect(
        &self,
        x1: i16,
        y1: i16,
        x2: i16,
        y2: i16,
        color: Color,
        fillstyle: FillStyle,
        pixelsize: u16,
    ) {
        unsafe {
            epic_disp_rect(x1, y1, x2, y2, color.0, fillstyle as u32, pixelsize);
        }
    }

    pub fn circ(&self, x: u16, y: u16, rad: u16, color: Color, fillstyle: FillStyle, pixelsize: u16) {
    /// Draw a circle
    pub fn circ(
        &self,
        x: i16,
        y: i16,
        rad: u16,
        color: Color,
        fillstyle: FillStyle,
        pixelsize: u16,
    ) {
        unsafe {
            epic_disp_circ(x, y, rad, color.0, fillstyle as u32, pixelsize);
        }
    }

    /// Obtain a handle for a framebuffer.
    ///
    /// Don't use `display.send()` but `framebuffer.send()` as long as
    /// it is in use.
    pub fn framebuffer<'d>(&'d mut self) -> FrameBuffer<'d> {
        FrameBuffer::uninitialized(self)
    }
}

impl Drop for Display {
    fn drop(&mut self) {
        unsafe { epic_disp_close(); }
    }
        unsafe {
            epic_disp_close();
        }
    }
}

/// Convenience text display
///
/// Requires `card10_alloc::init()` and `extern crate alloc;`
#[macro_export]
macro_rules! display {
    ($disp: expr, $x: expr, $y: expr, $fg: expr, $bg: expr,
     $fmt: expr) => ({
         use alloc::format;
         let s = format!(concat!($fmt, "\0"));
         $disp.print($x, $y, s.as_bytes(), $fg, $bg);
    });
    ($disp: expr, $x: expr, $y: expr, $fg: expr, $bg: expr,
     $fmt: expr, $($arg: tt)*) => ({
         use alloc::format;
         let s = format!(concat!($fmt, "\0"), $($arg)*);
         $disp.print($x, $y, s.as_bytes(), $fg, $bg);
    });
}

/// Convenience text display with selected font
///
/// Requires `card10_alloc::init()` and `extern crate alloc;`
#[macro_export]
macro_rules! display_adv {
    ($disp: expr, $font: tt, $x: expr, $y: expr, $fg: expr, $bg: expr,
     $fmt: expr) => ({
         use alloc::format;
         use card10_l0dable::Font;
         let s = format!(concat!($fmt, "\0"));
         $disp.print_adv(Font::$font, $x, $y, s.as_bytes(), $fg, $bg);
    });
    ($disp: expr, $font: tt, $x: expr, $y: expr, $fg: expr, $bg: expr,
     $fmt: expr, $($arg: tt)*) => ({
         use alloc::format;
         let s = format!(concat!($fmt, "\0"), $($arg)*);
         $disp.print_adv(Font::$font, $x, $y, s.as_bytes(), $fg, $bg);
    });
}
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ impl<'a> fmt::Write for FmtBuffer<'a> {
    }
}

/// 256 bytes ought to be enough for any string
/// 255 bytes ought to be enough for any C string
pub fn str_to_cstr(s: &str) -> [u8; 256] {
    let mut buf: [u8; 256] = unsafe { uninitialized() };
    let mut fmt = FmtBuffer::new(buf.as_mut());
+68 −0
Original line number Diff line number Diff line
use core::slice::from_raw_parts;

extern "C" {
    static Font8: Font;
    static Font12: Font;
    static Font16: Font;
    static Font20: Font;
    static Font24: Font;
}

#[repr(C)]
pub struct Font {
    table: *const u8,
    pub w: u16,
    pub h: u16,
}

impl Font {
    pub fn font8() -> &'static Self {
        unsafe { &Font8 }
    }
    pub fn font12() -> &'static Self {
        unsafe { &Font12 }
    }
    pub fn font16() -> &'static Self {
        unsafe { &Font16 }
    }
    pub fn font20() -> &'static Self {
        unsafe { &Font20 }
    }
    pub fn font24() -> &'static Self {
        unsafe { &Font24 }
    }
    
    fn bytes_per_row(&self) -> usize {
        self.w as usize / 8 + 1
    }
    
    pub fn get_glyph(&self, c: char) -> Option<Glyph> {
        let h = self.h as usize;
        let bytes_per_row = self.bytes_per_row();
        let table = unsafe {
            from_raw_parts(self.table, ('~' as usize - (' ' as usize) - 1) * bytes_per_row * h)
        };
        let offset = (c as usize - (' ' as usize)) * bytes_per_row * h;
        if offset < table.len() {
            let table = &table[offset..(offset + bytes_per_row * h)];
            Some(Glyph {
                table,
                bytes_per_row,
            })
        } else {
            None
        }
    }
}

pub struct Glyph<'a> {
    table: &'a [u8],
    bytes_per_row: usize,
}

impl<'a> Glyph<'a> {
    pub fn get_pixel(&self, x: usize, y: usize) -> bool {
        let offset = x / 8 + y * self.bytes_per_row;
        self.table[offset] & (1 << (7 - (x & 7))) != 0
    }
}
+163 −0
Original line number Diff line number Diff line
use crate::Display;
use card10_sys::*;
use core::mem::{transmute, uninitialized};
use core::ops::{Index, IndexMut};

use embedded_graphics::pixelcolor::{PixelColor, Rgb565};
use embedded_graphics::prelude::Pixel;
use embedded_graphics::Drawing;

mod font;
pub use font::*;
mod text;
pub use text::TextRenderer;

pub struct FrameBuffer<'d> {
    _display: &'d mut Display,
    buffer: disp_framebuffer,
}

impl<'d> FrameBuffer<'d> {
    pub fn uninitialized(display: &'d mut Display) -> Self {
        FrameBuffer {
            _display: display,
            buffer: unsafe { uninitialized() },
        }
    }

    pub fn send(&mut self) {
        unsafe {
            epic_disp_framebuffer(&mut self.buffer);
        }
    }

    pub fn clear(&mut self, color: RawColor) {
        for y in 0..Display::H {
            for x in 0..Display::W {
                let bytes: &mut RawColor =
                    unsafe { transmute(&mut self.buffer.fb[y as usize][x as usize]) };
                *bytes = color;
            }
        }
    }

    pub fn text<'a, 'f>(
        &'a mut self,
        x: isize,
        y: isize,
        font: &'f Font,
        color: RawColor,
    ) -> TextRenderer<'a, 'd, 'f> {
        TextRenderer {
            framebuffer: self,
            x,
            y,
            font,
            color,
        }
    }
}

impl<'d> Index<(u16, u16)> for FrameBuffer<'d> {
    type Output = RawColor;
    fn index(&self, (x, y): (u16, u16)) -> &Self::Output {
        let x = usize::from(Display::W as u16 - 1 - x);
        let y = usize::from(Display::H as u16 - 1 - y);
        unsafe { transmute(&self.buffer.fb[y][x]) }
    }
}

impl<'d> IndexMut<(u16, u16)> for FrameBuffer<'d> {
    fn index_mut(&mut self, (x, y): (u16, u16)) -> &mut Self::Output {
        let x = usize::from(Display::W as u16 - 1 - x);
        let y = usize::from(Display::H as u16 - 1 - y);
        unsafe { transmute(&mut self.buffer.fb[y][x]) }
    }
}

/// `embedded-graphics` support
impl<'d, C: PixelColor + Into<RawColor>> Drawing<C> for FrameBuffer<'d> {
    fn draw<T>(&mut self, item: T)
    where
        T: IntoIterator<Item = Pixel<C>>,
    {
        for Pixel(coord, color) in item {
            let x = coord[0] as u16;
            let y = coord[1] as u16;

            if x >= Display::W as u16 || y >= Display::H as u16 {
                continue;
            }
            // Swap bytes
            self[(x, y)] = color.into();
        }
    }
}

/// RGB565 with swapped bytes
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct RawColor([u8; 2]);

impl RawColor {
    pub fn red() -> Self {
        Self::rgb8(0xff, 0, 0)
    }

    pub fn blue() -> Self {
        Self::rgb8(0, 0, 0xff)
    }

    pub fn green() -> Self {
        Self::rgb8(0, 0xff, 0)
    }

    pub fn black() -> Self {
        Self::rgb8(0, 0, 0)
    }

    pub fn white() -> Self {
        Self::rgb8(0xff, 0xff, 0xff)
    }

    pub fn yellow() -> Self {
        Self::rgb8(0xff, 0xff, 0)
    }

    /// Construct from 0..255-triple
    pub fn rgb8(r8: u8, g8: u8, b8: u8) -> Self {
        let b1 = (r8 & 0xF8) | (g8 >> 5);
        let b2 = ((g8 & 0xFA) << 3) | (b8 >> 3);
        RawColor([b1, b2])
    }

    pub fn r8(&self) -> u8 {
        self.0[0] & 0xF8
    }

    pub fn g8(&self) -> u8 {
        ((self.0[0] & 0x7) << 5) |
        ((self.0[0] & 0xE) >> 3)
    }

    pub fn b8(&self) -> u8 {
        (self.0[1] & 0x1F) << 3
    }
}

impl PixelColor for RawColor {}

/// Most similar format, just byte-swapped
impl From<Rgb565> for RawColor {
    fn from(rgb: Rgb565) -> Self {
        let c = rgb.0;
        RawColor([(c >> 8) as u8, c as u8])
    }
}

/// Dummy implementation required for impl Drawable
impl From<u8> for RawColor {
    fn from(b: u8) -> Self {
        RawColor([0, b])
    }
}
+43 −0
Original line number Diff line number Diff line
use super::{Font, FrameBuffer, RawColor};
use crate::Display;
use core::fmt::Write;

pub struct TextRenderer<'a, 'd, 'f> {
    pub framebuffer: &'a mut FrameBuffer<'d>,
    pub x: isize,
    pub y: isize,
    pub font: &'f Font,
    pub color: RawColor,
}

impl<'a, 'd, 'f> Write for TextRenderer<'a, 'd, 'f> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        for c in s.chars() {
            self.write_char(c)?;
        }
        Ok(())
    }

    fn write_char(&mut self, c: char) -> core::fmt::Result {
        match self.font.get_glyph(c) {
            None => Ok(()),
            Some(glyph) => {
                for y in 0..self.font.h {
                    let y1 = (self.y + y as isize) as u16;
                    if y1 < Display::H as u16 {
                        for x in 0..self.font.w {
                            let x1 = (self.x + x as isize) as u16;
                            if x1 < Display::W as u16 {
                                if glyph.get_pixel(x as usize, y as usize) {
                                    self.framebuffer[(x1, y1)] = self.color;
                                }
                            }
                        }
                    }
                }
                self.x += self.font.w as isize;
                Ok(())
            }
        }
    }
}
Original line number Diff line number Diff line
use core::str::from_utf8_unchecked;
//! File System support (unfinished)

use core::mem::uninitialized;
use super::bindings::*;
use core::str::from_utf8_unchecked;

use card10_sys::*;

type Result<T> = core::result::Result<T, Error>;

@@ -28,9 +31,7 @@ impl From<i32> for Error {

pub fn read_dir(path: &str) -> Result<ReadDir> {
    let pathbuf = crate::str_to_cstr(path);
    Error::check(|| unsafe {
        epic_file_opendir(pathbuf.as_ptr())
    }).map(|fd| ReadDir { fd })
    Error::check(|| unsafe { epic_file_opendir(pathbuf.as_ptr()) }).map(|fd| ReadDir { fd })
}

pub struct ReadDir {
@@ -52,6 +53,7 @@ impl<'a> Iterator for ReadDir {
    }
}

#[allow(non_camel_case_types)]
#[repr(u8)]
#[derive(Copy, Clone)]
pub enum epic_stat_type {
@@ -60,13 +62,14 @@ pub enum epic_stat_type {
    Dir = 2,
}

#[allow(non_camel_case_types)]
#[repr(packed)]
#[derive(Copy, Clone)]
pub struct epic_stat {
    /// not u32 as generated by bindgen
    pub type_: epic_stat_type,
    pub size: u32,
    pub name: [super::ctypes::c_char; 256usize],
    pub name: [ctypes::c_char; 256usize],
    pub _reserved: [u8; 12usize],
}

@@ -95,20 +98,15 @@ impl DirStat {
    }

    pub fn name(&self) -> &str {
        let len = self.entry.name.iter().position(|b| *b == 0)
            .unwrap_or(0);
        unsafe {
            from_utf8_unchecked(&self.entry.name[0..len])
        }
        let len = self.entry.name.iter().position(|b| *b == 0).unwrap_or(0);
        unsafe { from_utf8_unchecked(&self.entry.name[0..len]) }
    }
}

pub fn rename(from: &str, to: &str) -> Result<()> {
    let frombuf = crate::str_to_cstr(from);
    let tobuf = crate::str_to_cstr(to);
    Error::check(|| unsafe {
        epic_file_rename(frombuf.as_ptr(), tobuf.as_ptr())
    }).map(|_| ())
    Error::check(|| unsafe { epic_file_rename(frombuf.as_ptr(), tobuf.as_ptr()) }).map(|_| ())
}

pub struct File {
@@ -119,21 +117,22 @@ impl File {
    pub fn open(path: &str) -> Result<File> {
        let pathbuf = crate::str_to_cstr(path);
        let modebuf = b"r\0";
        Error::check(|| unsafe {
            epic_file_open(pathbuf.as_ptr(), modebuf.as_ptr())
        }).map(|fd| File { fd })
        Error::check(|| unsafe { epic_file_open(pathbuf.as_ptr(), modebuf.as_ptr()) })
            .map(|fd| File { fd })
    }

    pub fn read<'a>(&mut self, buf: &'a mut [u8]) -> Result<&'a [u8]> {
        let bytes = Error::check(|| unsafe {
            epic_file_read(self.fd, buf.as_ptr() as *mut _, buf.len())
        })? as usize;
        let bytes =
            Error::check(|| unsafe { epic_file_read(self.fd, buf.as_ptr() as *mut _, buf.len()) })?
                as usize;
        Ok(&buf[0..bytes])
    }
}

impl Drop for File {
    fn drop(&mut self) {
        unsafe { epic_file_close(self.fd); }
        unsafe {
            epic_file_close(self.fd);
        }
    }
}
+55 −0
Original line number Diff line number Diff line
//! Lights

use card10_sys::*;

#[derive(Clone, Copy)]
pub enum LEDColor {
    RGB(u8, u8, u8),
    HSV(f32, f32, f32),
}

/// Update all RGB LEDs
///
/// `f` must supply a `LEDColor` for `0..=10`.
pub fn update_rgb_leds<F>(f: F)
where
    F: Fn(i32) -> LEDColor,
{
    for index in 0..=10 {
        let color = f(index);
        match color {
            LEDColor::RGB(r, g, b) => unsafe {
                epic_leds_prep(index, r, g, b);
            }
            LEDColor::HSV(h, s, v) => unsafe {
                epic_leds_prep_hsv(index, h, s, v);
            }
        }
        unsafe {
            epic_leds_update();
        }
    }
}

/// Set powersave mode for the RGB leds
///
/// Setting powersave mode disables the LEDs, saving
/// ~15 mA
pub fn set_rgb_leds_powersave(v: bool) {
    unsafe { epic_leds_set_powersave(v); }
}

#[repr(i32)]
pub enum Rocket {
    Blue = 0,
    Yellow = 1,
    Green = 2,
}

/// Set one of the rocket LEDs to the desired brightness.
///
/// Brightness should be 0 - 31; larger values will be clamped.
pub fn set_rocket(r: Rocket, brightness: u8) {
    let v = if brightness > 31{31} else {brightness}; // clamp
    unsafe { epic_leds_set_rocket(r as i32, v) }
}
+46 −0
Original line number Diff line number Diff line
//! [Repository](https://git.card10.badge.events.ccc.de/astro/rust-card10)
#![no_std]

mod os;
pub use os::*;
mod display;
pub use display::{Color, Display, FillStyle, Font, LineStyle};
mod buttons;
pub mod framebuffer;
pub use buttons::Buttons;
pub mod uart;
pub const UART: uart::Uart = uart::Uart;
mod light_sensor;
pub use light_sensor::LightSensor;
mod rtc;
pub mod trng;
pub mod vibra;
pub use rtc::{MilliSeconds, Seconds, Time};
mod fmt_buffer;
pub use fmt_buffer::{str_to_cstr, FmtBuffer};
mod bme680;
pub use bme680::BME680;
mod max86150;
pub use max86150::MAX86150;
mod bhi160;
pub use bhi160::{
    Accelerometer, Error as BHI160Error, Gyroscope, Orientation, Sensor as BHI160,
    SensorData as BHI160Data,
};
pub mod fs;
mod leds;
pub use leds::*;

/// Type check the user-supplied entry function.
#[macro_export]
macro_rules! main {
    ($path:path) => {
        #[export_name = "main"]
        pub unsafe fn __main() {
            // type check the given path
            let f: fn() = $path;

            f()
        }
    };
}
Original line number Diff line number Diff line
use super::bindings::*;
use card10_sys::*;

pub struct LightSensor;
/// Light sensor
pub struct LightSensor {
    // Prevent creation of this struct by all but this module.
    _private: (),
}

impl LightSensor {
    /// Start sensing light
    pub fn start() -> Self {
        if unsafe { epic_light_sensor_run() } != 0 {
            panic!("Cannot start light sensor");
        }
        LightSensor
        LightSensor { _private: () }
    }

    /// Obtain current light sensor reading
    pub fn get(&self) -> Option<u16> {
        let mut result = 0;
        if unsafe { epic_light_sensor_get(&mut result) } == 0 {
@@ -22,6 +28,8 @@ impl LightSensor {

impl Drop for LightSensor {
    fn drop(&mut self) {
        unsafe { epic_light_sensor_stop(); }
        unsafe {
            epic_light_sensor_stop();
        }
    }
}
+134 −0
Original line number Diff line number Diff line
// ECG and PPG sensor

pub struct MAX86150 {
    stream_id: i32,
}

use core::{mem::size_of, mem::MaybeUninit};

use card10_sys::*;

// TODO: redefine in common sensor
use crate::bhi160::Error;

const DATA_MAX: usize = 128;

impl MAX86150 {
    pub fn start() -> Result<Self, Error> {
        let mut cfg = max86150_sensor_config {
            sample_buffer_len: DATA_MAX,
            ppg_sample_rate: 200,
        };

        let stream_id =
            unsafe { epic_max86150_enable_sensor(&mut cfg, size_of::<max86150_sensor_config>()) };

        if stream_id < 0 {
            let error = match -stream_id {
                errno::EBUSY => Error::DriverBusy,
                _ => Error::Unknown(stream_id),
            };

            return Err(error);
        }

        Ok(MAX86150 { stream_id })
    }

    pub fn read(&self) -> Result<MAX86150SensorData, Error> {
        let mut buffer = MaybeUninit::<[max86150_sensor_data; DATA_MAX]>::zeroed();
        let buffer_pointer = buffer.as_mut_ptr() as *mut _;

        let packet_count = unsafe {
            epic_stream_read(
                self.stream_id,
                buffer_pointer,
                size_of::<max86150_sensor_data>() * DATA_MAX,
            )
        };
        if packet_count < 0 {
            let error = match -packet_count {
                errno::ENODEV => Error::SensorUnavailable,
                errno::EBADF => Error::SensorDescriptorUnknown,
                errno::EINVAL => Error::InvalidSampleCount,
                errno::EBUSY => Error::CouldNotAcquireLock,
                _ => Error::Unknown(packet_count),
            };
            return Err(error);
        }

        Ok(MAX86150SensorData {
            buf: unsafe { buffer.assume_init() },
            n: packet_count as usize,
        })
    }
}

pub struct MAX86150SensorData {
    buf: [max86150_sensor_data; DATA_MAX],
    n: usize,
}

impl MAX86150SensorData {
    pub const fn is_empty(&self) -> bool {
        self.n == 0
    }

    pub const fn len(&self) -> usize {
        self.n
    }
}

impl<'a> IntoIterator for &'a MAX86150SensorData {
    type Item = MAX86150SensorDataItem;
    type IntoIter = MAX86150SensorDataIterator<'a>;

    fn into_iter(self) -> Self::IntoIter {
        MAX86150SensorDataIterator { data: self, pos: 0 }
    }
}

pub struct MAX86150SensorDataIterator<'a> {
    data: &'a MAX86150SensorData,
    pos: usize,
}

#[derive(Debug, Clone)]
pub struct MAX86150SensorDataItem {
    pub red_raw: u32,
    pub ir_raw: u32,
    pub ecg_raw: i32,
}

impl MAX86150SensorDataItem {
    pub fn get_red(&self) -> u32 {
        self.red_raw
    }
}

impl<'a> Iterator for MAX86150SensorDataIterator<'a> {
    type Item = MAX86150SensorDataItem;
    fn next(&mut self) -> Option<Self::Item> {
        while self.pos < self.data.n {
            let vec = &self.data.buf[self.pos];
            let item = MAX86150SensorDataItem {
                red_raw: vec.red,
                ir_raw: vec.ir,
                ecg_raw: vec.ecg,
            };

            self.pos += 1;

            return Some(item);
        }
        None
    }
}

impl Drop for MAX86150 {
    fn drop(&mut self) {
        unsafe {
            epic_max86150_disable_sensor();
        }
    }
}
Original line number Diff line number Diff line
use super::bindings::*;
use card10_sys::*;

/// Execute Python script or ELF file
pub fn exec(path: &str) -> ! {
    let mut pathbuf = super::str_to_cstr(path);
    unsafe {
@@ -8,16 +9,18 @@ pub fn exec(path: &str) -> ! {
    unreachable!()
}

/// Exit current l0dable
pub fn exit(ret: i32) -> ! {
    unsafe {
        epic_exit(ret);
    }
    unreachable!()
    loop {}
}

/// Cause a reboot
pub fn system_reset() -> ! {
    unsafe {
        epic_system_reset();
    }
    unreachable!()
    loop {}
}
+64 −0
Original line number Diff line number Diff line
//! Real-time clock functionality

use card10_sys::*;
use core::ops::Sub;

/// Implemented for `Seconds` and `Milliseconds`
pub trait Time {
    /// Get current time
    fn time() -> Self;
    /// Set the time (TODO)
    fn set_time(&self);
}

#[derive(Clone, Copy, Debug)]
pub struct Seconds(pub u32);

impl From<MilliSeconds> for Seconds {
    fn from(ms: MilliSeconds) -> Seconds {
        Seconds((ms.0 / 1000) as u32)
    }
}

impl Time for Seconds {
    fn time() -> Self {
        let s = unsafe { epic_rtc_get_seconds() };
        Seconds(s)
    }
    /// TODO
    fn set_time(&self) {
    }
}

impl Sub for Seconds {
    type Output = Seconds;
    fn sub(self, rhs: Seconds) -> Self::Output {
        Seconds(self.0 - rhs.0)
    }
}

#[derive(Clone, Copy, Debug)]
pub struct MilliSeconds(pub u64);

impl From<Seconds> for MilliSeconds {
    fn from(s: Seconds) -> MilliSeconds {
        MilliSeconds(s.0 as u64 * 1000)
    }
}

impl Time for MilliSeconds {
    fn time() -> Self {
        let ms = unsafe { epic_rtc_get_milliseconds() };
        MilliSeconds(ms)
    }
    /// TODO
    fn set_time(&self) {
    }
}

impl Sub for MilliSeconds {
    type Output = MilliSeconds;
    fn sub(self, rhs: MilliSeconds) -> Self::Output {
        MilliSeconds(self.0 - rhs.0)
    }
}
Original line number Diff line number Diff line
use super::bindings::*;
//! True Random Number Generator

use card10_sys::*;

/// Read bytes from the True Random Number Generated
pub fn read(dest: &mut [u8]) -> bool {
    unsafe { epic_trng_read(dest.as_mut_ptr(), dest.len()) != 0 }
}
+34 −0
Original line number Diff line number Diff line
use core::fmt::Write;

use card10_sys::*;

/// USB UART, 115200 baud
///
/// Supports use of `write!(Uart, "{}", ...);` Use `println!(...);`
/// for convenience.
pub struct Uart;

impl Write for Uart {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        unsafe {
            epic_uart_write_str(s.as_ptr(), s.len());
        }
        Ok(())
    }
}

#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ($crate::uart::_print(format_args!($($arg)*)));
}

#[macro_export]
macro_rules! println {
    () => ($crate::print!("\r"));
    ($($arg:tt)*) => ($crate::print!("{}\r\n", format_args!($($arg)*)));
}

#[doc(hidden)]
pub fn _print(args: core::fmt::Arguments) {
    crate::UART.write_fmt(args).ok();
}
+17 −0
Original line number Diff line number Diff line
//! Vibration motor

use card10_sys::*;

/// Set vibration motor to off/on
pub fn set(status: bool) {
    unsafe {
        epic_vibra_set(status.into());
    }
}

/// Vibrate for a duration
pub fn vibrate(millis: i32) {
    unsafe {
        epic_vibra_vibrate(millis);
    }
}

card10-sys/Cargo.toml

0 → 100644
+59 −0
Original line number Diff line number Diff line
[package]

name = "card10-sys"
version = "1.10.0"
authors = [
    "Astro <astro@spaceboyz.net>",
    "Kloenk <me@kloenk.de>",
    "Bruno Kirschner <bruno.kirschner@online.de>",
    "Puzzlewolf <camp@nora.pink>",
    "Raphael Nestler <raphael.nestler@gmail.com>",
    "Danilo Bargen <mail@dbrgn.ch>",
    "toon <toon@c3d2.de>",
    "Sergey Pepyakin <sergei@parity.io>",

]
license = "MIT/Apache-2.0"
edition = "2018"

description = "Unsafe C bindings for card10 l0dables"
repository = "https://git.card10.badge.events.ccc.de/astro/rust-card10"
homepage = "https://git.card10.badge.events.ccc.de/astro/rust-card10"
documentation = "https://docs.rs/card10-sys"

categories = ["external-ffi-bindings", "no-std"]
keywords = [
    "CCC",
    "CCCamp2019",
    "CCCamp19",
    "card10",
    "l0dable",
]

exclude = [
    "target",
    "firmware/lib/micropython",
    "firmware/lib/ff13",
    "firmware/lib/sdk/Documentation",
    "firmware/lib/sdk/Applications",
    "firmware/lib/sdk/Libraries/BTLE",
    "firmware/lib/sdk/Libraries/FreeRTOS",
    "firmware/lib/sdk/Libraries/FreeRTOS-Plus",
    "firmware/lib/sdk/Libraries/SDHC",
    "firmware/lib/sdk/Libraries/MAXUSB",
    "firmware/openocd",
    "firmware/Documentation",
    "firmware/hw-tests",
    "firmware/preload",
    "firmware/pycardium",
    "firmware/bootloader",
    "firmware/tools",
    "firmware/docker",
]

[dependencies]
r0 = "^0.2"

[build-dependencies]
bindgen = "^0.52"
cc = "^1.0"

card10-sys/build.rs

0 → 100644
+64 −0
Original line number Diff line number Diff line
use std::{env, error::Error, fs, path::PathBuf};

fn main() -> Result<(), Box<dyn Error>> {
    let out = env::var_os("OUT_DIR")
        .map(|path| PathBuf::from(path))
        .ok_or("OUT_DIR definition missing")?;

    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=vendor/l0dable.ld");
    println!("cargo:rerun-if-changed=vendor/client.c");
    println!("cargo:rerun-if-changed=vendor/wrapper.h");
    println!("cargo:rustc-link-search={}", out.display());

    // Linker script
    fs::copy("vendor/crt.s", out.join("crt.s"))?;
    fs::copy("vendor/l0dable.ld", out.join("l0dable.ld"))?;

    // Link against C code
    cc::Build::new()
        .target("thumbv7em-none-eabi")
        .compiler("arm-none-eabi-gcc")
        .opt_level_str("s")
        .debug(true)
        .define("TARGET", "MAX32665")
        .define("TARGET_UC", "MAX32665")
        .define("TARGET_LC", "max32665")
        .define("TARGET_REV", "0x4131")
        .define("BOARD", "card10")
        .flag("-fPIE")
        .flag("-pie")
        .include("firmware/epicardium")
        .include("firmware/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Include")
        .include("firmware/lib/sdk/Libraries/CMSIS/Include")
        .include("firmware/lib/sdk/Libraries/MAX32665PeriphDriver/Include")
        .file("firmware/lib/sdk/Libraries/MAX32665PeriphDriver/Source/sema.c")
        .file("firmware/lib/sdk/Libraries/MAX32665PeriphDriver/Source/mxc_assert.c")
        .file("firmware/l0dables/lib/hardware.c")
        .file("firmware/epicardium/api/caller.c")
        .file("firmware/lib/gfx/Fonts/font12.c")
        .file("firmware/lib/gfx/Fonts/font16.c")
        .file("firmware/lib/gfx/Fonts/font20.c")
        .file("firmware/lib/gfx/Fonts/font24.c")
        .file("firmware/lib/gfx/Fonts/font8.c")
        .file("vendor/client.c")
        .compile("card10");

    // Generate bindings to C code
    let bindings = bindgen::Builder::default()
        .use_core()
        .clang_arg("-Isrc")
        .clang_arg("-Ifirmware/epicardium")
        .clang_arg("-Ifirmware/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Include")
        .clang_arg("-Ifirmware/lib/sdk/Libraries/CMSIS/Include")
        .clang_arg("-Ifirmware/lib/sdk/Libraries/MAX32665PeriphDriver/Include")
        .header("vendor/wrapper.h")
        .ctypes_prefix("ctypes")
        .opaque_type("epic_stat")
        .generate()
        .map_err(|_| "Couldn't generate bindings")?;

    bindings.write_to_file(out.join("bindings.rs"))?;

    Ok(())
}

firmware @ da4bc11f

Original line number Diff line number Diff line
Subproject commit da4bc11fbdded3588a97b1f905da4866a30f520c

card10-sys/src/lib.rs

0 → 100644
+132 −0
Original line number Diff line number Diff line
//! This gets linked with the client C code for the card10 EPIC API.

#![no_std]
#![feature(global_asm)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
#![feature(core_intrinsics)]
#![feature(panic_info_message)]

use core::fmt::Write;
use core::intrinsics;
use core::panic::PanicInfo;

pub struct Uart;

impl Write for Uart {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        unsafe {
            epic_uart_write_str(s.as_ptr(), s.len());
        }
        Ok(())
    }
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    write!(Uart, "panicked").ok();

    if let Some(message) = info.message() {
        write!(Uart, " at '{}'", message).ok();
    }

    if let Some(location) = info.location() {
        write!(
            Uart,
            ", {}:{}:{}",
            location.file(),
            location.line(),
            location.column()
        )
        .ok();
    }
    writeln!(Uart, "\r").ok();
    unsafe { intrinsics::abort() }
}

global_asm!(include_str!(concat!(env!("OUT_DIR"), "/crt.s")));
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

/// Initialization for l0dables
#[link_section = ".text.boot"]
#[no_mangle]
pub unsafe extern "C" fn Reset_Handler() -> ! {
    extern "C" {
        fn SystemInit();
        // Boundaries of the .bss section, provided by the linker script
        static mut __bss_start: u32;
        static mut __bss_end: u32;
    }

    // Zeroes the .bss section
    r0::zero_bss(&mut __bss_start, &mut __bss_end);
    SystemInit();

    extern "Rust" {
        fn main();
    }

    main();

    epic_exit(0);
    unreachable!()
}

pub mod ctypes {
    pub type c_short = i16;
    pub type c_ushort = u16;
    pub type c_int = i32;
    pub type c_uint = u32;
    pub type c_long = i32;
    pub type c_ulong = u32;
    pub type c_longlong = i64;
    pub type c_ulonglong = u64;
    pub type c_char = u8;
    pub type c_schar = i8;
    pub type c_uchar = u8;
    pub use core::ffi::c_void;
}

pub mod errno {
    use crate::ctypes::c_int;

    pub const EPERM: c_int = 1; // Operation not permitted
    pub const ENOENT: c_int = 2; // No such file or directory
    pub const ESRCH: c_int = 3; // No such process
    pub const EINTR: c_int = 4; // Interrupted system call
    pub const EIO: c_int = 5; // I/O error
    pub const ENXIO: c_int = 6; // No such device or address
    pub const E2BIG: c_int = 7; // Argument list too long
    pub const ENOEXEC: c_int = 8; // Exec format error
    pub const EBADF: c_int = 9; // Bad file number
    pub const ECHILD: c_int = 10; // No child processes
    pub const EAGAIN: c_int = 11; // Try again
    pub const ENOMEM: c_int = 12; //Out of memory
    pub const EACCES: c_int = 13; //Permission denied
    pub const EFAULT: c_int = 14; //Bad address
    pub const ENOTBLK: c_int = 15; //Block device required
    pub const EBUSY: c_int = 16; //Device or resource busy
    pub const EEXIST: c_int = 17; //File exists
    pub const EXDEV: c_int = 18; //Cross-device link
    pub const ENODEV: c_int = 19; //No such device
    pub const ENOTDIR: c_int = 20; //Not a directory
    pub const EISDIR: c_int = 21; //Is a directory
    pub const EINVAL: c_int = 22; //Invalid argument
    pub const ENFILE: c_int = 23; //File table overflow
    pub const EMFILE: c_int = 24; //Too many open files
    pub const ENOTTY: c_int = 25; //Not a typewriter
    pub const ETXTBSY: c_int = 26; //Text file busy
    pub const EFBIG: c_int = 27; //File too large
    pub const ENOSPC: c_int = 28; //No space left on device
    pub const ESPIPE: c_int = 29; //Illegal seek
    pub const EROFS: c_int = 30; //Read-only file system
    pub const EMLINK: c_int = 31; //Too many links
    pub const EPIPE: c_int = 32; //Broken pipe
    pub const EDOM: c_int = 33; //Math argument out of domain of func
    pub const ERANGE: c_int = 34; //Math result not representable
    pub const EAFNOSUPPORT: c_int = 97; //Address family not supported by protocol
    pub const ECONNRESET: c_int = 104; //Connection timed out
    pub const ETIMEDOUT: c_int = 110; //Connection timed out
    pub const EINPROGRESS: c_int = 115; //Operation now in progress
}
+23 −17
Original line number Diff line number Diff line
let
  mozillaOverlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
  pkgs = import <nixpkgs> { overlays = [ mozillaOverlay ]; };
in
{ mozillaOverlay ? import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz),
  pkgs ? import <nixpkgs> { overlays = [ mozillaOverlay ]; },
  firmwareSrc ? ./card10-sys/firmware,
}:
with pkgs;
let
  openocd = callPackage ./openocd.nix {};
  rust = rustChannelOfTargets "nightly" null [ "thumbv7em-none-eabi" ];
  rustPlatform = makeRustPlatform {
    rustc = rust;
@@ -12,7 +11,7 @@ let
  };
  epic-stubs = stdenv.mkDerivation {
    name = "epic-stubs";
    src = ./c;
    src = firmwareSrc;
    buildInputs = [ gcc python3 ];
    buildPhase = ''
      ${python3}/bin/python epicardium/api/genapi.py -H epicardium/epicardium.h -c client.c -s server.c
@@ -22,27 +21,34 @@ let
      cp client.c server.c $out/
    '';
  };
  firmware = rustPlatform.buildRustPackage rec {
  l0dables = rustPlatform.buildRustPackage rec {
    name = "rust-card10";
    version = "0.0.0";
    src = ./.;
    cargoSha256 = "10qv30p3kr570glnyn37b6r8pgx48zj0mr9qf84m4wk4sjp3wxsd";
    buildInputs = [ pkgsCross.arm-embedded.stdenv.cc glibc_multi ];
    cargoSha256 = "16vchzfk50crr7kbiy84d1spq072ywa7s5jz886nvh7hah94w4a1";
    buildInputs = [ pkgsCross.arm-embedded.stdenv.cc ];
    prePatch = ''
      cp ${epic-stubs}/client.c l0dable/src/
      cp ${epic-stubs}/client.c card10-sys/vendor/
    '' + lib.optionalString (firmwareSrc != ./card10-sys/firmware) ''
      rm -r card10-sys/firmware
      cp -r ${firmwareSrc} card10-sys/firmware
    '';
    NIX_DEBUG=1;
    LIBCLANG_PATH="${llvmPackages.libclang}/lib";
    CARGO_HOME="$(mktemp -d cargo-home.XXX)";
    preBuild = ''
      export LIBCLANG_PATH=${llvmPackages.libclang}/lib
      export CPATH=${glibc_multi.dev}/include
      export CARGO_HOME=$(mktemp -d cargo-home.XXX)
      cd example
      export CPATH="${glibc_multi.dev}/include:${stdenv.cc.cc}/lib/gcc/$(cc -dumpmachine)/${lib.getVersion pkgsCross.arm-embedded.stdenv.cc.cc}/include"
    '';
    doCheck = false;
    installPhase = ''
      mkdir -p $out/lib
      cp target/thumbv7em-none-eabi/release/example $out/lib/example.elf
      mkdir -p $out/apps
      for f in target/thumbv7em-none-eabi/release/* ; do
        if [ -x $f ] && [ ! -d $f ] ; then
          cp $f $out/apps/$(basename $f).elf
        fi
      done
    '';
  };
in {
  inherit rust rustPlatform firmware epic-stubs;
  inherit rust rustPlatform l0dables epic-stubs;
}
Original line number Diff line number Diff line
[package]
edition = "2018"
name = "l0dable"
name = "draw-image"
version = "0.0.0"
authors = ["Astro <astro@spaceboyz.net>"]
authors = ["Rafael Caricio <crates.rs@caric.io>"]
edition = "2018"

[dependencies]
r0 = "0.2"
panic-abort = "0.3"
card10-l0dable = { path = "../card10-l0dable" }
embedded-graphics = { version = "0.5.2" }

[build-dependencies]
cc = "1.0"
bindgen = "0.51"
 No newline at end of file

[[bin]]
name = "draw-image"
path = "src/main.rs"

draw-image/README.md

0 → 100644
+16 −0
Original line number Diff line number Diff line
Draw Image
==========

Images need to be converted to a bitmap of 16BPP for inclusion with `include_bytes!()`. You can convert an image using:

```
convert image.png -flip -type truecolor -define bmp:subtype=RGB565 -depth 16 -strip image.bmp
```

then

```
tail -c $bytes image.bmp > image.raw // where $bytes is w * h * 2
```

This will remove the BMP header leaving the raw pixel data E.g 160x80 image will have 160 * 80 * 2 bytes of raw data.
 No newline at end of file

draw-image/src/main.rs

0 → 100644
+26 −0
Original line number Diff line number Diff line
#![no_std]
#![no_main]

use card10_l0dable::*;
use embedded_graphics::prelude::*;

use embedded_graphics::image::Image16BPP;
use embedded_graphics::pixelcolor::Rgb565;

main!(main);
fn main() {
    let mut display = Display::open();
    let mut framebuffer = display.framebuffer();

    let image: Image16BPP<Rgb565> =
        Image16BPP::new(include_bytes!("applewatch-160x80.raw"), 160, 80);
    framebuffer.draw(&image);
    framebuffer.send();

    loop {
        let buttons = Buttons::read();
        if buttons.left_top() {
            break;
        }
    }
}
Original line number Diff line number Diff line
@@ -5,7 +5,8 @@ version = "0.0.0"
authors = ["Astro <astro@spaceboyz.net>"]

[dependencies]
l0dable = { path = "../l0dable" }
card10-l0dable = { path = "../card10-l0dable" }
card10-alloc = { path = "../card10-alloc" }

[build-dependencies]
cc = "1.0"
Original line number Diff line number Diff line
#![no_std]
#![no_main]

use core::fmt::Write;
use l0dable::*;
use core::fmt::{self, Write};
use card10_l0dable::*;
/// Allows you to `use alloc::*;`
extern crate alloc;

main!(main);
fn main() {
    writeln!(UART, "Hello from Rust\r").unwrap();
    let heap_size = card10_alloc::init(4096);
    println!("Heap size: {}", heap_size);

    let result = run();
    if let Err(error) = result {
        writeln!(UART, "error: {}\r", error).unwrap();
    }
}

fn run() -> Result<(), Error> {
    writeln!(UART, "Hello from Rust\r")?;

    let bme = BME680::start();
    let a = BHI160::<Accelerometer>::start();
    let g = BHI160::<Gyroscope>::start();
    let o = BHI160::<Orientation>::start();
    let a = BHI160::<Accelerometer>::start()?;
    let g = BHI160::<Gyroscope>::start()?;
    let o = BHI160::<Orientation>::start()?;

    set_rocket(Rocket::Blue, 20);
    set_rocket(Rocket::Yellow, 20);
    set_rocket(Rocket::Green, 10);

    let display = Display::open();
    let light = LightSensor::start();

    for t in 0..Display::W {
        writeln!(UART, "BME: {:?}\r", bme.read()).unwrap();
        writeln!(UART, "A:\r").unwrap();
        for d in &a.read() {
            writeln!(UART, " - {:?}\r", d).unwrap();
        writeln!(UART, "BME: {:?}\r", bme.read())?;
        writeln!(UART, "A:\r")?;

        for d in &a.read()? {
            writeln!(UART, " - {:?}\r", d)?;
        }
        writeln!(UART, "O:\r").unwrap();
        for d in &o.read() {
            writeln!(UART, " - {:?}\r", d).unwrap();

        writeln!(UART, "O:\r")?;
        for d in &o.read()? {
            writeln!(UART, " - {:?}\r", d)?;
        }
        writeln!(UART, "G:\r").unwrap();
        for d in &g.read() {
            writeln!(UART, " - {:?}\r", d).unwrap();

        writeln!(UART, "G:\r")?;
        for d in &g.read()? {
            writeln!(UART, " - {:?}\r", d)?;
        }

        display.clear(Color::yellow());
        display.print(160 - t, 10, b"Hello Rust\0", Color::white(), Color::black());
        display!(display, 160 - t, 10, Color::white(), Color::black(), "Hello Rust {}", t);

        let b = Buttons::read();
        if b.left_bottom() {
            display.print(0, 60, b"LB\0", Color::red(), Color::black());
            display!(display, 0, 60, Color::red(), Color::black(), "LB");
            vibra::set(true);
        }
        if b.right_bottom() {
            display.print(80, 60, b"RB\0", Color::red(), Color::black());
            display!(display, 80, 60, Color::red(), Color::black(), "RB");
            vibra::set(false);
        }
        if b.left_top() {
            display.print(0, 10, b"LT\0", Color::red(), Color::black());
            display!(display, 0, 10, Color::red(), Color::black(), "LT");
        }
        if b.right_top() {
            display.print(80, 10, b"RT\0", Color::red(), Color::black());
            display!(display, 80, 10, Color::red(), Color::black(), "RT");
        }
        if b.right_top() {
            display.print(80, 30, b"Reset\0", Color::red(), Color::black());
            display!(display, 80, 30, Color::red(), Color::black(), "Reset");
        }
        writeln!(UART, "Light: {:?}\r", light.get()).unwrap();
        writeln!(UART, "Light: {:?}\r", light.get())?;

        display.update();
    }

    Ok(())
}

// -----------------------------------------------------------------------------
// Error
// -----------------------------------------------------------------------------

#[derive(Debug)]
pub enum Error {
    UartWriteFailed(fmt::Error),
    SensorInteractionFailed(BHI160Error),
}

impl From<fmt::Error> for Error {
    fn from(error: fmt::Error) -> Self {
        Error::UartWriteFailed(error)
    }
}

impl From<BHI160Error> for Error {
    fn from(error: BHI160Error) -> Self {
        Error::SensorInteractionFailed(error)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::UartWriteFailed(error) => error.fmt(f),
            Error::SensorInteractionFailed(error) => error.fmt(f),
        }
    }
}

firmware.nix

0 → 100644
+39 −0
Original line number Diff line number Diff line
{ pkgs ? import <nixpkgs> {},
  src ? ./.,
  srcPath ? "card10-sys/firmware",
}:
with pkgs;

let
  cSrc = stdenv.mkDerivation rec {
    name = "card10-src";
    inherit src;
    phases = [ "unpackPhase" "patchPhase" "installPhase" ];
    nativeBuildInputs = [ git ];
    prePatch = "cd ${srcPath}";
    patches = [
      ./0001-hack-epicardium-reduce-init-delay-from-2s-to-0.1s.patch
    ];
    postPatch = ''
      VERSION="$(git -C ${src} describe --always)"
      GITHASH="$(git -C ${src} rev-parse HEAD)"

      substituteInPlace tools/version-header.sh \
        --replace "\$VERSION" "$VERSION" \
        --replace "\$GITHASH" "$GITHASH" \
        --replace "git -C" echo
    '';
    installPhase = ''
      cp -ar . $out

      mkdir -p $out/nix-support
      for f in $out/*.bin ; do
        echo file binary-dist $f >> $out/nix-support/hydra-build-products
      done
    '';
  };
  firmware = lib.makeOverridable (attrs: {
    inherit (attrs) src;
    firmware = import attrs.src;
  }) { src = cSrc; };
in firmware

hydra/combined.nix

0 → 100644
+32 −0
Original line number Diff line number Diff line
{ pkgs ? import <nixpkgs> {},
  cFirmware ? <c-firmware>,
  rkanoid ? <rkanoid>,
}:
with pkgs;

let
  l0dables = buildEnv {
    name = "l0dables";
    paths = [ rkanoid ];
    pathsToLink = [ "/apps" ];
  };
  release = import ../release.nix {
    inherit cFirmware;
    rustL0dables = l0dables;
  };
  releaseZip = stdenv.mkDerivation {
    name = "card10-combined.zip";
    nativeBuildInputs = [ release zip ];
    phases = [ "installPhase" ];
    installPhase = ''
      mkdir -p $out/nix-support

      cd ${release}/
      zip -9r $out/firmware.zip .
      echo file binary-dist $out/firmware.zip >> $out/nix-support/hydra-build-products
    '';
  };
in {
  release = lib.hydraJob release;
  release-zip = lib.hydraJob releaseZip;
}

hydra/firmware.nix

0 → 100644
+52 −0
Original line number Diff line number Diff line
{ pkgs ? import <nixpkgs> {},
}:
with pkgs;

let
  firmwareSrc = import ../firmware.nix {
    inherit pkgs;
    src = <firmware>;
    srcPath = ".";
  };
  firmwareGit = firmwareSrc.override (oldArgs: {
    src = oldArgs.src.overrideAttrs (oldAttrs: {
      name = "${oldAttrs.name}-git";
      # no more git, .git is dropped by Hydra
      nativeBuildInputs = [];
      postPatch = ''
        VERSION="0.0-git"
        GITHASH="0000000000000000000000000000000000000000"

        substituteInPlace tools/version-header.sh \
          --replace "\$VERSION" "$VERSION" \
          --replace "\$GITHASH" "$GITHASH" \
          --replace "git -C" echo
      '';
    });
  });
  firmware = firmwareGit.firmware.overrideAttrs (oldAttrs: {
    buildCommand = ''
      ${oldAttrs.buildCommand}

      mkdir -p $out/nix-support
      for f in $out/**/*.elf $out/card10/*.bin ; do
        echo file binary-dist $f >> $out/nix-support/hydra-build-products
      done
    '';
  });
  firmwareZip = stdenv.mkDerivation {
    name = "card10-firmware.zip";
    nativeBuildInputs = [ firmware zip ];
    phases = [ "installPhase" ];
    installPhase = ''
      mkdir -p $out/nix-support

      cd ${firmware}/card10/
      zip -9r $out/firmware.zip .
      echo file binary-dist $out/firmware.zip >> $out/nix-support/hydra-build-products
    '';
  };
in {
  firmware = lib.hydraJob firmware;
  firmware-zip = lib.hydraJob firmwareZip;
}

hydra/hydra.json

0 → 100644
+17 −0
Original line number Diff line number Diff line
{
    "enabled": 1,
    "type": 0,
    "hidden": true,
    "description": "Jobsets",
    "nixexprinput": "rust-card10",
    "nixexprpath": "hydra/hydra.nix",
    "checkinterval": 300,
    "schedulingshares": 100,
    "enableemail": false,
    "emailoverride": "astro@spaceboyz.net",
    "keepnr": 30,
    "inputs": {
        "nixpkgs": { "type": "git", "value": "https://github.com/NixOS/nixpkgs-channels nixos-19.09", "emailresponsible": false },
        "rust-card10": { "type": "git", "value": "https://git.card10.badge.events.ccc.de/astro/rust-card10.git", "emailresponsible": false }
    }
}

hydra/hydra.nix

0 → 100644
+68 −0
Original line number Diff line number Diff line
{ pkgs ? import <nixpkgs> {} }:
let
  jobsets = {
    c-firmware = {
      enabled = 1;
      type = 0;
      hidden = false;
      description = "card10 C firmware";
      nixexprinput = "rust-card10";
      nixexprpath = "hydra/firmware.nix";
      checkinterval = 300;
      schedulingshares = 100;
      enableemail = true;
      emailoverride = "astro@spaceboyz.net";
      keepnr = 3;
      inputs = {
        firmware = { type = "git"; value = "https://git.card10.badge.events.ccc.de/card10/firmware.git"; emailresponsible = false; };
        rust-card10 = { type = "git"; value = "https://git.card10.badge.events.ccc.de/astro/rust-card10.git"; emailresponsible = false; };
        nixpkgs = { type = "git"; value = "git://github.com/NixOS/nixpkgs.git release-19.09"; emailresponsible = false; };
      };
    };
    rust-l0dables = {
      enabled = 1;
      type = 0;
      hidden = false;
      description = "card10 Rust l0dable examples";
      nixexprinput = "rust-card10";
      nixexprpath = "hydra/l0dables.nix";
      checkinterval = 300;
      schedulingshares = 100;
      enableemail = true;
      emailoverride = "astro@spaceboyz.net";
      keepnr = 3;
      inputs = {
        firmware = { type = "git"; value = "https://git.card10.badge.events.ccc.de/card10/firmware.git"; emailresponsible = false; };
        rust-card10 = { type = "git"; value = "https://git.card10.badge.events.ccc.de/astro/rust-card10.git"; emailresponsible = false; };
        nixpkgs = { type = "git"; value = "git://github.com/NixOS/nixpkgs.git release-19.09"; emailresponsible = false; };
        mozillaOverlay = { type = "git"; value = "git://github.com/mozilla/nixpkgs-mozilla.git"; emailresponsible = false; };
      };
    };
    firmware-combined = {
      enabled = 1;
      type = 0;
      hidden = false;
      description = "Prepared firmware with Rust l0dables";
      nixexprinput = "rust-card10";
      nixexprpath = "hydra/combined.nix";
      checkinterval = 300;
      schedulingshares = 100;
      enableemail = true;
      emailoverride = "astro@spaceboyz.net";
      keepnr = 3;
      inputs = {
        rust-card10 = { type = "git"; value = "https://git.card10.badge.events.ccc.de/astro/rust-card10.git"; emailresponsible = false; };
        c-firmware = { type = "build"; value = "rust-card10:c-firmware:firmware"; emailresponsible = false; };
        rkanoid = { type = "build"; value = "rust-card10:rust-l0dables:rkanoid"; emailresponsible = false; };
        nixpkgs = { type = "git"; value = "git://github.com/NixOS/nixpkgs.git release-19.09"; emailresponsible = false; };
      };
    };
  };

  jobsetsJson = pkgs.writeText "jobsets.json" (builtins.toJSON jobsets );
in
{
  jobsets = pkgs.runCommand "rust-card10.json" {} ''
    cp ${jobsetsJson} $out
  '';
}

hydra/l0dables.nix

0 → 100644
+40 −0
Original line number Diff line number Diff line
{ rustManifest ? ./channel-rust-nightly.toml,
}:
let
  mozillaOverlay = import <mozillaOverlay>;
  manifestOverlay = self: super: {
    rustChannelOfTargets = _channel: _date: targets:
      (super.lib.rustLib.fromManifestFile rustManifest {
        inherit (super) stdenv fetchurl patchelf;
      }).rust.override { inherit targets; };
  };
  pkgs = (import <nixpkgs> { overlays = [ mozillaOverlay manifestOverlay ]; });
  project = import ../default.nix {
    inherit pkgs mozillaOverlay;
    firmwareSrc = <firmware>;
  };
  l0dable = crate: project.l0dables.overrideAttrs (oldAttrs: {
    name = "${oldAttrs.name}-${crate}";
    buildPhase = ''
      pushd ${crate}
      ${oldAttrs.buildPhase}
      popd
    '';
    installPhase = ''
      ${oldAttrs.installPhase}

      mkdir -p $out/nix-support
      for f in $out/apps/*.elf ; do
        echo file binary-dist $f >> $out/nix-support/hydra-build-products
      done
    '';
  });
  crates =
    pkgs.lib.filterAttrs (crate: type:
      type == "directory" &&
      builtins.pathExists (<rust-card10> + "/${crate}/Cargo.toml")
    ) (builtins.readDir <rust-card10>);
in
builtins.mapAttrs (crate: type:
  pkgs.lib.hydraJob (l0dable crate)
) crates

l0dable/build.rs

deleted100644 → 0
+0 −62
Original line number Diff line number Diff line
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
    println!("cargo:rerun-if-changed=build.rs");

    // Linker script
    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
    File::create(out.join("l0dable.ld"))
        .unwrap()
        .write_all(include_bytes!("l0dable.ld"))
        .unwrap();
    println!("cargo:rustc-link-search={}", out.display());
    println!("cargo:rerun-if-changed=l0dable.ld");

    // Link against C code
    cc::Build::new()
        .target("thumbv7em-none-eabi")
        .compiler("arm-none-eabi-gcc")
        .define("TARGET", "MAX32665")
        .define("TARGET_UC", "MAX32665")
        .define("TARGET_LC", "max32665")
        .define("TARGET_REV", "0x4131")
        .define("BOARD", "card10")
        .opt_level_str("s")
        .debug(true)
        .flag("-fPIE").flag("-pie")
        .include("../c/epicardium")
        .include("../c/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Include")
        .include("../c/lib/sdk/Libraries/CMSIS/Include")
        .include("../c/lib/sdk/Libraries/MAX32665PeriphDriver/Include")
        .file("../c/lib/sdk/Libraries/MAX32665PeriphDriver/Source/sema.c")
        .file("../c/lib/sdk/Libraries/MAX32665PeriphDriver/Source/mxc_assert.c")
        .file("../c/l0dables/lib/hardware.c")
        .file("../c/epicardium/api/caller.c")
        .file("src/client.c")
        .compile("card10");
    println!("cargo:rerun-if-changed=src/client.rs");

    // Generate bindings to C code
    let bindings = bindgen::Builder::default()
        .clang_args(&[
            "-Isrc",
            "-I../c/epicardium",
            "-I../c/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Include",
            "-I../c/lib/sdk/Libraries/CMSIS/Include",
            "-I../c/lib/sdk/Libraries/MAX32665PeriphDriver/Include",
        ])
        .header("src/bindings.h")
        .use_core()
        .ctypes_prefix("super::ctypes")
        .generate()
        .expect("Unable to generate bindings");
    println!("cargo:rerun-if-changed=src/bindings.h");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

l0dable/src/errno.h

deleted100644 → 0
+0 −0
Original line number Diff line number Diff line

l0dable/src/lib.rs

deleted100644 → 0
+0 −89
Original line number Diff line number Diff line
#![no_std]
#![feature(global_asm)]

use panic_abort as _;

global_asm!(include_str!("crt.s"));

/// Type check the user-supplied entry function.
#[macro_export]
macro_rules! main {
    ($path:path) => {
        #[export_name = "main"]
        pub unsafe fn __main() {
            // type check the given path
            let f: fn() = $path;

            f()
        }
    };
}

#[link_section = ".text.boot"]
#[no_mangle]
pub unsafe extern "C" fn Reset_Handler() -> ! {
    extern "C" {
        fn SystemInit();
        // Boundaries of the .bss section, provided by the linker script
        static mut __bss_start: u64;
        static mut __bss_end: u64;
    }

    // Zeroes the .bss section
    r0::zero_bss(&mut __bss_start, &mut __bss_end);
    SystemInit();

    extern "Rust" {
        fn main();
    }

    main();
    exit(0);
}

pub mod ctypes {
    #![allow(non_camel_case_types)]

    pub type c_short = i16;
    pub type c_ushort = u16;
    pub type c_int = i32;
    pub type c_uint = u32;
    pub type c_long = i32;
    pub type c_ulong = u32;
    pub type c_longlong = i64;
    pub type c_ulonglong = u64;
    pub type c_char = u8;
    pub type c_schar = i8;
    pub type c_uchar = u8;
    pub use core::ffi::c_void;
}

pub mod bindings {
    #![allow(non_upper_case_globals)]
    #![allow(non_camel_case_types)]
    #![allow(non_snake_case)]

    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

mod os;
pub use os::*;
mod display;
pub use display::{Display, Color, LineStyle, FillStyle};
mod buttons;
pub use buttons::Buttons;
pub mod uart;
pub const UART: uart::Uart = uart::Uart;
mod light_sensor;
pub use light_sensor::LightSensor;
pub mod vibra;
pub mod trng;
mod utime;
pub use utime::time;
mod fmt_buffer;
pub use fmt_buffer::{FmtBuffer, str_to_cstr};
mod bme680;
pub use bme680::BME680;
mod bhi160;
pub use bhi160::{Sensor as BHI160, Accelerometer, Orientation, Gyroscope, SensorData as BHI160Data};
pub mod fs;

l0dable/src/uart.rs

deleted100644 → 0
+0 −13
Original line number Diff line number Diff line
use core::fmt::Write;
use super::bindings::*;

pub struct Uart;

impl Write for Uart {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        unsafe {
            epic_uart_write_str(s.as_ptr(), s.len() as isize);
        }
        Ok(())
    }
}

l0dable/src/utime.rs

deleted100644 → 0
+0 −5
Original line number Diff line number Diff line
use super::bindings::*;

pub fn time() -> u32 {
    unsafe { epic_rtc_get_seconds() }
}

l0dable/src/vibra.rs

deleted100644 → 0
+0 −9
Original line number Diff line number Diff line
use super::bindings::*;

pub fn set(status: bool) {
    unsafe { epic_vibra_set(status.into()); }
}

pub fn vibrate(millis: i32) {
    unsafe { epic_vibra_vibrate(millis); }
}

openocd.gdb

deleted100644 → 0
+0 −12
Original line number Diff line number Diff line
target remote :3333

define reset
    mon mww 0x40000004 0x80000000
end

# print demangled symbols by default
set print asm-demangle on

monitor arm semihosting enable

load

openocd.nix

deleted100644 → 0
+0 −40
Original line number Diff line number Diff line
{ stdenv, makeWrapper, openocd, fetchFromGitHub, autoreconfHook, git, which }:
let
  maxim-openocd = openocd.overrideAttrs (oa: {
      name = "maxim-openocd";
      src = fetchFromGitHub {
        owner = "maximmbed";
        repo = "openocd";
        rev = "e71ac88c9dbfa4ee1405d7a86376119dcc887ed1";
        sha256 = "18yc1wyclmjxqg6jilfcm60hi01pgqc4dilsmksqbhg23m6x4ycw";
        fetchSubmodules = true;
      };
      nativeBuildInputs = oa.nativeBuildInputs ++ [
        autoreconfHook
        git
        which
      ];
      enableParallelBuilding = true;
    });
  card10-scripts = stdenv.mkDerivation {
    name = "card10-scripts";
    src = ./c/openocd/scripts;
    dontBuild = true;
    installPhase = ''
      mkdir -p $out/share/openocd
      cp -ar . $out/share/openocd/scripts
   '';
  };
in
  stdenv.mkDerivation {
    name = "openocd-card10";
    src = maxim-openocd;
    phases = [ "unpackPhase" "installPhase" ];
    buildInputs = [ makeWrapper maxim-openocd card10-scripts ];
    installPhase = ''
      mkdir -p $out/bin
      makeWrapper ${maxim-openocd}/bin/openocd $out/bin/openocd-card10 \
        --add-flags "-f ${card10-scripts}/share/openocd/scripts/interface/cmsis-dap.cfg" \
        --add-flags "-f ${card10-scripts}/share/openocd/scripts/target/max32665.cfg"
    '';
  }

python-crc16.nix

deleted100644 → 0
+0 −10
Original line number Diff line number Diff line
{ python3Packages }:

python3Packages.buildPythonPackage rec {
  pname = "crc16";
  version = "0.1.1";
  src = python3Packages.fetchPypi {
    inherit pname version;
    sha256 = "15nkx0pa4lskwin84flpk8fsw3jqg6wic6v3s83syjqg76h6my61";
  };
}

release.nix

0 → 100644
+22 −0
Original line number Diff line number Diff line
{ pkgs ? import <nixpkgs> {},
  cFirmware ? (import ./firmware.nix { inherit pkgs; }).firmware,
  rustL0dables ? (import ./default.nix {}).l0dables,
}:
with pkgs;

stdenv.mkDerivation {
  name = "card10-firmware";
  buildInputs = [ cFirmware rustL0dables ];
  phases = [ "installPhase" ];
  installPhase = ''
    mkdir $out
    cp -r ${cFirmware}/card10/* $out/

    chmod u+w $out/apps
    cp ${rustL0dables}/apps/* $out/apps/

    cat << EOF > $out/card10.cfg
    execute_elf=true
    EOF
  '';
}
Original line number Diff line number Diff line
@@ -5,7 +5,9 @@ version = "0.0.0"
authors = ["Astro <astro@spaceboyz.net>"]

[dependencies]
l0dable = { path = "../l0dable" }
card10-l0dable = { path = "../card10-l0dable" }
card10-alloc = { path = "../card10-alloc" }
embedded-graphics = "0.5"

[build-dependencies]
cc = "1.0"
+158 −95
Original line number Diff line number Diff line
#![no_std]
#![no_main]

extern crate alloc;
use core::fmt::Write;
use l0dable::*;
use card10_l0dable::{*, framebuffer::*};
use embedded_graphics::{
    prelude::*,
    primitives::Circle,
};

pub const BALL_RADIUS: u16 = 4;
pub const PADDLE_SPACE: u16 = 32;
pub const BLOCK_W: u16 = 16;
pub const BLOCK_H: u16 = 10;
pub const BLOCKS_X: u16 = Display::W / BLOCK_W;
pub const BLOCKS_Y: u16 = (Display::H - PADDLE_SPACE) / BLOCK_H;
pub const BALL_RADIUS: u32 = 4;
pub const PADDLE_SPACE: u32 = 32;
pub const PADDLE_HEIGHT: u32 = 6;
pub const PADDLE_SPEED: u32 = 4;
pub const BLOCK_W: u32 = 16;
pub const BLOCK_H: u32 = 10;
pub const BLOCKS_X: u32 = Display::W as u32 / BLOCK_W;
pub const BLOCKS_Y: u32 = (Display::H as u32 - PADDLE_SPACE) / BLOCK_H;
pub const LED_INTERVAL: u64 = 70;

pub struct Blocks {
    blocks: [[Option<Color>; BLOCKS_X as usize]; BLOCKS_Y as usize],
    blocks: [[Option<RawColor>; BLOCKS_X as usize]; BLOCKS_Y as usize],
}

impl Blocks {
@@ -28,7 +36,7 @@ impl Blocks {
                let mut buf = [0, 0, 0, 0];
                trng::read(&mut buf);
                if buf[0] <= rand_max {
                    *block = Some(Color::rgb8(
                    *block = Some(RawColor::rgb8(
                       0x80 | buf[1],
                       0x80 | buf[2],
                       0x80 | buf[3],
@@ -41,18 +49,18 @@ impl Blocks {
        result
    }

    pub fn collides(&mut self, x: u16, y: u16) -> bool {
        let col = (x / BLOCK_W) as usize;
        let line = (y / BLOCK_H) as usize;
        if line >= BLOCKS_Y.into() || col >= BLOCKS_X.into() {
            return false;
    pub fn collides(&mut self, x: u32, y: u32) -> Option<RawColor> {
        let col = x / BLOCK_W;
        let line = y / BLOCK_H;
        if line >= BLOCKS_Y || col >= BLOCKS_X {
            return None;
        }
        match self.blocks[line][col] {
            Some(_) => {
                self.blocks[line][col] = None;
                true
        match self.blocks[line as usize][col as usize] {
            Some(color) => {
                self.blocks[line as usize][col as usize] = None;
                Some(color)
            }
            None => false,
            None => None,
        }
    }

@@ -61,9 +69,6 @@ impl Blocks {
    }
}

const PADDLE_HEIGHT: u16 = 6;
const PADDLE_SPEED: u16 = 4;

#[derive(Clone, Copy, Debug)]
enum Direction {
    /// Up Right
@@ -77,7 +82,7 @@ enum Direction {
}

impl Direction {
    fn motion(&self, x: &mut u16, y: &mut u16) {
    fn motion(&self, x: &mut u32, y: &mut u32) {
        use Direction::*;
        match self {
            UR => {
@@ -163,117 +168,166 @@ enum GameResult {
}

fn game(level: u16, mut score: u32) -> GameResult {
    let start_time = time();
    let display = Display::open();
    let start_time = Seconds::time();
    let mut display = Display::open();
    let mut fb = display.framebuffer();
    let accel = BHI160::<Accelerometer>::start().unwrap();
    let mut accel_x = 0;
    let mut leds = [LEDColor::RGB(0, 0, 0); 6];
    let mut last_led_tick = MilliSeconds::time();

    let mut paddle = Display::W / 2;
    let paddle_size = 18 - (2 * level).min(14);
    let mut paddle = Display::W as u32 / 2;
    let paddle_size = 18 - (2 * level as u32).min(14);
    let mut ball_x = paddle;
    let mut ball_y = Display::H - PADDLE_HEIGHT - BALL_RADIUS;
    let mut ball_y = Display::H as u32 - PADDLE_HEIGHT - BALL_RADIUS;
    let mut ball_direction = Direction::UR;
    let mut blocks = Blocks::generate((0x3F + 0x10 * level).min(0xff) as u8);

    // Clear screen
    draw_rect(&mut fb, 0, 0, Display::W as u16 - 1, Display::H as u16 - 1, RawColor::black());
    // Draw Blocks
    for (lineno, line) in blocks.blocks.iter_mut().enumerate() {
        let lineno = lineno as u32;
        for (colno, block) in line.iter_mut().enumerate() {
            block.map(|color| {
                let colno = colno as u32;
                draw_rect(&mut fb, (colno * BLOCK_W) as u16, (lineno * BLOCK_H) as u16,
                          ((colno + 1) * BLOCK_W - 1) as u16, ((lineno + 1) * BLOCK_H - 1) as u16,
                          color);
                // TODO: spacing?
            });
        }
    }

    for tick in 0.. {
        for data in &accel.read().unwrap() {
            accel_x = (17.0 * data.get_x()) as i32;
        }
        let input = Buttons::read();
        let old_paddle = paddle;
        if input.left_bottom() {
            paddle -= PADDLE_SPEED;
            paddle = paddle.max(paddle_size);
            paddle = paddle.saturating_sub(PADDLE_SPEED);
        }
        if input.right_bottom() {
            paddle += PADDLE_SPEED;
            paddle = paddle.min(Display::W - paddle_size)
        }
        let paddle_moving = paddle != old_paddle;
        if accel_x < 0 {
            paddle += -accel_x as u32;
        } else if accel_x > 0 {
            paddle = paddle.saturating_sub(accel_x as u32);
        }
        paddle = paddle
            .max(paddle_size)
            .min(Display::W as u32 - paddle_size);
        if input.left_top() {
            exit(0);
        }
        let mut check_finish = false;
        let speed_steps = 2 + (time() - start_time) / 15;
        let speed_steps = 2 + (Seconds::time() - start_time).0 / 15;
        let speed_steps = (speed_steps >> 1) +
            (speed_steps & tick & 1);
        for _ in 0..speed_steps {
            ball_direction.motion(&mut ball_x, &mut ball_y);

            if (ball_direction.is_left() && ball_x <= BALL_RADIUS) ||
                (ball_direction.is_right() && ball_x >= Display::W - BALL_RADIUS) {
                (ball_direction.is_right() && ball_x >= Display::W as u32 - BALL_RADIUS) {
                    // Bounce on left/right border
                    ball_direction.bounce(Bounce::Vertical);
                    vibra::vibrate(10);
                    vibra::vibrate(5);
            }
            if ball_direction.is_up() && ball_y <= BALL_RADIUS {
                // Bounce on top border
                ball_direction.bounce(Bounce::Horizontal);
                vibra::vibrate(10);
                vibra::vibrate(5);
            }
            if ball_direction.is_down() &&
                ball_y >= Display::H - PADDLE_HEIGHT - BALL_RADIUS &&
                ball_y >= Display::H as u32 - PADDLE_HEIGHT - BALL_RADIUS &&
                ball_x >= paddle - paddle_size &&
                ball_x <= paddle + paddle_size {
                    // Bounce on paddle
                    if paddle_moving && input.left_bottom() {
                    if paddle < old_paddle {
                        ball_direction = Direction::UL;
                        vibra::vibrate(50);
                    } else if paddle_moving && input.right_bottom() {
                        vibra::vibrate(20);
                    } else if paddle > old_paddle {
                        ball_direction = Direction::UR;
                        vibra::vibrate(50);
                        vibra::vibrate(20);
                    } else {
                        ball_direction.bounce(Bounce::Horizontal);
                        vibra::vibrate(20);
                        vibra::vibrate(10);
                    }
                }
            if ball_y >= Display::H - BALL_RADIUS {
            if ball_y >= Display::H as u32 - BALL_RADIUS {
                return GameResult::Over(score);
            }
            if blocks.collides(ball_x - BALL_RADIUS, ball_y) ||
                blocks.collides(ball_x + BALL_RADIUS, ball_y) {
            let collision_v = blocks.collides(ball_x - BALL_RADIUS, ball_y)
                .or_else(|| blocks.collides(ball_x + BALL_RADIUS, ball_y));
            if let Some(color) = collision_v {
                    ball_direction.bounce(Bounce::Vertical);
                    score += 3;
                    // paddle_size += 2;
                    check_finish = true;
                    vibra::vibrate(60);
                    vibra::vibrate(15);
                    leds[0] = LEDColor::RGB(color.r8(), color.g8(), color.b8());
                }
            if blocks.collides(ball_x, ball_y - BALL_RADIUS) ||
                blocks.collides(ball_x, ball_y + BALL_RADIUS) {
            let collision_h = blocks.collides(ball_x, ball_y - BALL_RADIUS)
                .or_else(|| blocks.collides(ball_x, ball_y + BALL_RADIUS));
            if let Some(color) = collision_h {
                    ball_direction.bounce(Bounce::Horizontal);
                    score += 2;
                    // paddle_size += 1;
                    check_finish = true;
                    vibra::vibrate(40);
                    vibra::vibrate(15);
                    leds[0] = LEDColor::RGB(color.r8(), color.g8(), color.b8());
                }
        }
        if check_finish && blocks.is_finished() {
            return GameResult::LevelFinish(score + 100);
        }

        display.clear(Color::black());
        // Paddle
        display.rect(
            paddle - paddle_size, Display::H - PADDLE_HEIGHT,
            paddle + paddle_size, Display::H - 1,
            Color::rgb8(0x7f, 0xff, 0x7f), FillStyle::Filled, 1
        );
        // Ball
        display.circ(
            ball_x, ball_y, BALL_RADIUS,
            Color::rgb8(0x7f, 0x7f, 0xff), FillStyle::Filled, 1
        );
        // Blocks
        // Empty blocks
        for (lineno, line) in blocks.blocks.iter_mut().enumerate() {
            let lineno = lineno as u16;
            let lineno = lineno as u32;
            for (colno, block) in line.iter_mut().enumerate() {
                block.map(|color| {
                    let colno = colno as u16;
                    display.rect(
                        colno * BLOCK_W,
                        lineno * BLOCK_H,
                        (colno + 1) * BLOCK_W - 1,
                        (lineno + 1) * BLOCK_H - 1,
                        color, FillStyle::Filled, 1
                block.or_else(|| {
                    let colno = colno as u32;
                    draw_rect(&mut fb, (colno * BLOCK_W) as u16, (lineno * BLOCK_H) as u16,
                              ((colno + 1) * BLOCK_W - 1) as u16, ((lineno + 1) * BLOCK_H - 1) as u16,
                              RawColor::black());
                    None
                });
            }
        }
        // Space below blocks
        draw_rect(&mut fb, 0, (BLOCK_H * BLOCKS_Y) as u16,
                  Display::W as u16, Display::H as u16, RawColor::black());
        // Paddle
        draw_rect(&mut fb,
                  (paddle - paddle_size) as u16, Display::H as u16 - PADDLE_HEIGHT as u16,
                  (paddle + paddle_size) as u16, Display::H as u16 - 1,
                  RawColor::rgb8(0x7f, 0xff, 0x7f));
        // Ball
        fb.draw(
            Circle::new((ball_x, ball_y).into(), BALL_RADIUS)
                .fill(Some(RawColor::rgb8(0x7f, 0x7f, 0xff)))
        );
        fb.send();

        update_rgb_leds(|index| {
            if index < 6 {
                leds[(5 - index) as usize]
            } else {
                leds[(index - 5) as usize]
            }
        });

        let now = MilliSeconds::time();
        if last_led_tick.0 + LED_INTERVAL <= now.0 {
            for i in 1..leds.len() {
                leds[leds.len() - i] = leds[leds.len() - i - 1];
            }
            leds[0] = LEDColor::RGB(0, 0, 0);
            last_led_tick = now;
        }
        display.update();
    }
    unreachable!()
}
@@ -281,10 +335,10 @@ fn game(level: u16, mut score: u32) -> GameResult {
fn title_screen() {
    let display = Display::open();
    display.clear(Color::red());
    display.print(30, 15, b"Rkanoid\0", Color::white(), Color::red());
    display.print(30, 30, b"in Rust\0", Color::white(), Color::red());
    display.print(20, 45, b"by Astro\0", Color::white(), Color::red());
    display.print(Display::W - 2 * Display::FONT_W, Display::H - Display::FONT_H, b"Go\0", Color::yellow(), Color::blue());
    display_adv!(display, Font24, Display::W / 2 - 60, 14, Color::white(), Color::red(), "Rkanoid");
    display_adv!(display, Font20, Display::W / 2 - 49, 40, Color::white(), Color::red(), "in Rust");
    display_adv!(display, Font16, Display::W / 2 - 44, 64, Color::white(), Color::red(), "by Astro");
    display!(display, Display::W - 2 * Display::FONT_W, Display::H - Display::FONT_H + 3, Color::yellow(), Color::blue(), "Go");
    display.update();
    while !Buttons::read().right_bottom() {}
}
@@ -292,13 +346,11 @@ fn title_screen() {
fn game_over(score: u32) -> bool {
    let display = Display::open();
    display.clear(Color::red());
    display.print(30, 0, b"Rkanoid\0", Color::white(), Color::red());
    display.print(0, 25, b"Game over!\0", Color::white(), Color::red());
    let mut score_str = [0u8; 32];
    write!(FmtBuffer::new(&mut score_str), "Score {}\0", score).unwrap();
    display.print(0, 60, &score_str, Color::white(), Color::red());
    display.print(0, 0, b"Q\0", Color::yellow(), Color::blue());
    display.print(Display::W - Display::FONT_W, Display::H - 2 * Display::FONT_H, b"S\0", Color::yellow(), Color::blue());
    display!(display, 30, 0, Color::white(), Color::red(), "Rkanoid");
    display!(display, 0, 25, Color::white(), Color::red(), "Game over!");
    display!(display, 0, 60, Color::white(), Color::red(), "Score {}", score);
    display!(display, 0, 0, Color::yellow(), Color::blue(), "Q");
    display!(display, Display::W - Display::FONT_W, Display::H - 2 * Display::FONT_H, Color::yellow(), Color::blue(), "S");
    display.update();
    loop {
        let buttons = Buttons::read();
@@ -314,15 +366,11 @@ fn game_over(score: u32) -> bool {
fn level_finish(level: u16, score: u32) -> bool {
    let display = Display::open();
    display.clear(Color::red());
    display.print(30, 0, b"Rkanoid\0", Color::white(), Color::red());
    let mut level_str = [0u8; 32];
    write!(FmtBuffer::new(&mut level_str), "Lvl {} done!\0", level + 1).unwrap();
    display.print(0, 25, &level_str, Color::white(), Color::red());
    let mut score_str = [0u8; 32];
    write!(FmtBuffer::new(&mut score_str), "Score {}\0", score).unwrap();
    display.print(0, 60, &score_str, Color::white(), Color::red());
    display.print(0, 0, b"Q\0", Color::yellow(), Color::blue());
    display.print(Display::W - Display::FONT_W, Display::H - 2 * Display::FONT_H, b"S\0", Color::yellow(), Color::blue());
    display!(display, 30, 0, Color::white(), Color::red(), "Rkanoid");
    display!(display, 0, 25, Color::white(), Color::red(), "Lvl {} done!", level + 1);
    display!(display, 20, 60, Color::white(), Color::red(), "Score {}", score);
    display!(display, 0, 60, Color::yellow(), Color::blue(), "Q");
    display!(display, Display::W - Display::FONT_W, Display::H - 2 * Display::FONT_H, Color::yellow(), Color::blue(), "S");
    display.update();
    loop {
        let buttons = Buttons::read();
@@ -337,13 +385,16 @@ fn level_finish(level: u16, score: u32) -> bool {

main!(main);
fn main() {
    card10_alloc::init(128 * 1024);
    title_screen();
    
    let mut quit = false;
    let mut level = 0;
    let mut score = 0;
    while !quit {
        match game(level, score) {
        let game_result = game(level, score);
        update_rgb_leds(|_| LEDColor::RGB(0, 0, 0));
        match game_result {
            GameResult::LevelFinish(new_score) => {
                let again = level_finish(level, new_score);
                quit = !again;
@@ -359,3 +410,15 @@ fn main() {
        }
    }
}

fn draw_rect(fb: &mut FrameBuffer, x1: u16, y1: u16, x2: u16, y2: u16, color: RawColor) {
    let x1 = x1.max(0).min(Display::W as u16 - 1);
    let x2 = x2.max(0).min(Display::W as u16 - 1);
    let y1 = y1.max(0).min(Display::H as u16 - 1);
    let y2 = y2.max(0).min(Display::H as u16 - 1);
    for y in y1..=y2 {
        for x in x1..=x2 {
            fb[(x, y)] = color;
        }
    }
}
+4 −7
Original line number Diff line number Diff line
{ pkgs ? import <nixpkgs> {} }:

with pkgs;
with import ./default.nix;
with import ./default.nix {};

stdenv.mkDerivation {
  name = "env";
@@ -10,14 +10,11 @@ stdenv.mkDerivation {
    glibc_multi
    rust
    pkgsCross.arm-embedded.stdenv.cc
    openocd
  ];

  # Set Environment Variables
  RUST_BACKTRACE = 1;

  LIBCLANG_PATH="${llvmPackages.libclang}/lib";
  shellHook = ''
    export LIBCLANG_PATH=${llvmPackages.libclang}/lib
    echo "Run 'cd example && cargo build --release'"
    export CPATH="${glibc_multi.dev}/include:${stdenv.cc.cc}/lib/gcc/$(cc -dumpmachine)/${lib.getVersion pkgsCross.arm-embedded.stdenv.cc.cc}/include"
    echo "Run 'cargo build --release'"
  '';
}