Release 0.4.0 of AVRoxide is now packaged up and released, with a host of new features…

No more custom toolchain

Thanks to bugfixes in the mainline Rust toolchain, we no longer need to use a custom toolchain to build AVRoxide applications = regular old Rust nightly will now do the job. Special thanks to Patryk27 for the critical bugfixes!

Storage Drivers

An abstraction layer for implementing storage using I2C bus attached serial devices has now been implemented. The avrox-storage crate provides both the abstraction and initial implementation for BR24T1M_3AM serial EEPROMs, but the design is sufficiently generic that other devices can be trivially supported.

Simple Filesystem Support

Once we have comparatively large storage, we want to be able to manage that storage. SNaFuS is a simple filesystem layer that sits on top of the storage drivers to provide a familiar, file-based interface to the storage, with the kind of format, create, open, read/write semantics you would expect.

The key differentiator between SNaFuS and “proper” filesystems like FAT (!) is that we use integers instead of strings to identify files, and do not support directories. Because who has enough spare code space for storing strings?

#[avr_oxide::main(chip="atmega4809",stacksize=1024)]
pub fn main() -> ! {
  let supervisor = avr_oxide::oxide::instance();

  // We will access the EEPROM over a serial bus
  let bus = StaticWrap::new(OxideSerialBus::using_bus(hardware::twi::twi0::instance().mux(hardware::twi::twi0::TwiPins::MasterASlaveC).mode(InterfaceMode::I2C, PortSpeed::Fast)));
  supervisor.listen(bus.borrow());
  
  // The EEPROM listens on address 0xA0
  let bus_client = OxideSerialBus::client(bus.borrow(), TwiAddr::addr(0xA0));
  
  // Create the storage driver
  let storage    = serprom::BR24T1M_3AM::using_client(bus_client);
  
  // And a filesystem that uses it
  let mut fs     = SnafusFileSystem::with_driver(PageBuffer::<32,_>::with_driver(storage));
  
  // Format the filesystem
  if !fs.is_formatted().unwrap() {
    fs.format().unwrap();
  }

  // Create a file ('named' 1)
  let mut file = File::create_on(fs, FileUid::with_uid(1)).unwrap();
  file.write_all(&MY_TEST_DATA).unwrap();
  file.sync_all().unwrap();

  supervisor.run()
}

Graphics Drivers

We also now have an abstraction for bitmapped display drivers, provided in the avrox-display crate. This provides an interface to bitmap devices and also a simple set of graphics primitives (like boxes, fills, and bitmap images) to draw on the bitmap.

The graphics interface is optimised for device memory usage rather than speed: we never store the entire display bitmap in memory, but rather compute the bits to write to the graphics device on-demand when the display is painted. This means you won’t be using it for animation, but for rendering simple UIs including dynamic elements it will do the job and not consume all your memory regardless of the display size.

#[avr_oxide::main(chip="atmega4809",stacksize=1024)]
pub fn main() -> ! {
  let supervisor = avr_oxide::oxide::instance();

  // We attach to the EEPROM with our graphics files, and the graphics device,
  // using a serial bus
  let bus = StaticWrap::new(OxideSerialBus::using_bus(hardware::twi::twi0::instance().mux(hardware::twi::twi0::TwiPins::MasterASlaveC).mode(InterfaceMode::I2C, PortSpeed::Standard)));
  supervisor.listen(bus.borrow());

  // Set up our storage drivers
  let storagebus = OxideSerialBus::client(bus, TwiAddr::addr(0xA0));
  let fs         = StaticWrap::new(SnafusFileSystem::with_driver(PageBuffer::<32,_>::with_driver(serprom::BR24T1M_3AM::using_client(storagebus))));

  // And also our display drivers
  let displaybus = OxideSerialBus::client(bus, avrox_display::displays::displayvisions::DVEAW096016DriverConfig::DEFAULT_I2C);
  let display = StaticWrap::new(displays::DisplayVisionsEAW096016::using_pin_and_client(board::pin_d(13), displaybus));
  
  // Initialise the display driver
  display.borrow().reset().unwrap();
  display.borrow().request_power_level(PowerLevel::Normal).unwrap();
  display.borrow().set_double_buffering(false).unwrap();

  // Now set up what we want to display
  static mut BIG_COUNTER : u32 = 0x00;
  let mut big_value = StaticWrap::new(RefCell::new(Some(0x00u32)));

  // We want a 7-segment display counter, scaled to double size, that will
  // use BIG_COUNTER as a data source
  let counter = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<5,16,_,_>::new(Grey::GREY75PC, Grey::GREY25PC, unsafe {big_value.borrow().static_ref()}));

  
  // We store a logo on the file system
  if !fs.borrow().is_formatted().unwrap() {
    println!("Filesystem not formatted!");
    panic!();
  }

  let logo_file = File::open_on(unsafe { fs.borrow().static_ref() }, ID_AVROXIDE_LOGO).unwrap();
  let logo = MonochromeImage::from(ImageFile::with_file(logo_file).unwrap());

  // Our scene comprises the logo and the counter, arranged left to
  // right, overlaid on a plain black background:
  let scene = Overlay::new(
    HorizontalPair::<_,_,position::Beginning>::new(logo, counter),
    SolidFill::new(Monochromatic::BLACK)
  );

  // First time round we'll render the whole scene.  We can then use
  // render_changed() to only update dynamic parts (e.g. the counter)
  display.borrow().render(&scene).unwrap();
  
  supervisor.run()
}

Trivial Bitmap Image File format

Since we have storage, and we also have graphics, it’d be nice to be able to display images stored as files on EEPROMs on the graphics display, right? To support this we also have a trivial image format, TBFF, which we can store as a file on a SNaFuS filesystem, and graphics primitives that support a filehandle as a datasource for the image to display.

The file format currently supports monochrome (single-bit) and greyscale (8 bit) image layers, along with room for RGB. An individual file may contain one or multiple of such layers, allowing for the application/graphics layer to pick the appropriate layer for some level of device independence.

A trivial “real computer” commandline application for converting more familiar graphics formats (like JPEG or PNG) into TBFF files is provided in my AVRoxide Utilities repo.

pub static DATA_AVROXIDE_LOGO: [u8; 1177] = [
  0x54u8, 0x42u8, 0x46u8, 0x47u8, 0x2u8, 0x0u8, 0x40u8, 0x0u8, 0x10u8, // Header, index follows
  0x0u8, 0x0u8, 0x0u8, 0x19u8,   // Monochrome layer offset
  0x0u8, 0x0u8, 0x0u8, 0x99u8,   // Greyscale layer offset
  0x0u8, 0x0u8, 0x0u8, 0x0u8,   // No RGBA4 layer
  0x0u8, 0x0u8, 0x0u8, 0x0u8,   // No RGBA8 layer

  // Monochrome layer data:
  0x0u8, 0x0u8, 0x0u8, /* .. snipped .. */ 0x1cu8, 0x0u8, 0x0u8,

  // Greyscale layer data:
  0x0u8, 0x0u8, 0x0u8, /* .. snipped .. */ 0x0u8, 0x0u8, 0x0u8,

  // RGBA4 layer data skipped

  // RGBA8 layer data skipped
];