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
];