Megatiny_hal
Megatiny_hal is a hardware abstraction layer for attiny and atmega avr devices that I will be continually developing. The development of this package used both atdf2svd and svd2rust. This page will only discuss basic usage; for more information regarding all the package's features, refer to this documentation.
Usage
I used a windows os with a linux subsystem although it is not necessary to use. For how to set that up, this is a helpful video.
Installing Dependencies
Firstly, the installation of rust can be found on the rust-lang site. This package requires nightly rust rather than stable rust, so run rustup toolchain install nightly to install it. Then for each project involving this package, run rustup override set nightly to use nightly rust for the project.
Avr-gcc is used as a linker, so it also needs to be installed, but the version installed via ubuntu's apt package manager is too old (version 5.4.0). I found downloads for version 12.1.0 on this site. Avrdude is used to flash the code onto the microcontroller, and do note that for a linux subsystem, since linux doesn't use COM ports, the linux version of avrdude cannot be used and the windows version must be used outside of the linux bash terminal to access the COM ports. The download can be found here.
Next, for rust to find avr-gcc, it must be added to path. On linux, unless an up to date version can be found on a package manager, the path to the avr-gcc binary from the download above must be added to /etc/environment by running sudo nano /etc/environment and adding the path to it separated from the other paths in it by a colon. Then source /etc/environment must be run each time a new bash window is opened. On windows, just add it to the path system environment variable. Though not necessary, the same is highly recommended for avrdude for convenience.
Crate Setup
After initializing the crate with cargo new or cargo init, add megatiny_hal as a dependency with cargo add megatiny_hal. Also run cargo add panic_halt. Then in the megatiny_hal repo, copy the files corresponding to your microcontroller from the build_files, device_files, and json_spec_files folders and add them to the root directory. Rename the files from build_files and device_files to build.rs and device.x respectively. Then create a folder called .cargo and add a file called config in it. Inside the config file, add the following to tell cargo what the target is:
[build] target = "[your json spec file].json"
Lastly, in the cargo.toml file add the following to enable link time optimization:
[profile.release] lto = true
Code Example
At the top of main.rs for any project add the following:
#![no_std] #![no_main]
This will omit the standard library, which has a lot of unnecessary features for microcontrollers, and it will not have the rust main function that gets compiled to an executable. Instead, it will have a main function that will be compiled down to machine code that avr-gcc can link to. Also after that, use extern crate panic_halt; to specify panic behavior.
It is also recommended to have the following use statements (some of these are specific to the attiny412; just replace attiny412 with your microcontroller)
use core::ops::Deref; use::megatiny_hal::attiny412; use megatiny_hal::attiny412::{TimeUnit, Pin, PinSelect}; use megatiny_hal::{Pin, GPIO};
Note that the including of a Pin struct as well as a PinSelect enum is necessary to use the megatiny_hal::Pin macro, and the including of the megatiny_hal::GPIO trait is necessary to use the methods associated with a GPIO pin.
Now finally for the main function, declare it like so so that avr-gcc can link to it:
#[no_mangle] pub extern "C" fn main() { ... }
The following code is an example of a blink code inside the main function:
let periphs = attiny412::get_periphs(); // gets the peripherals attiny412::init_timer(&periphs, TimeUnit::Ms); // initializes the timer let led_pin = Pin!(&periphs, PA3 output); // creates an led pin for PA3 as an output loop { led_pin.toggle(); // toggles the led pin attiny412::delay(&periphs, 1000); // delays for 1000 milliseconds (1 second) }
Building and Flashing
Now to build the program, run cargo build -Z build-std=core --release and that will create a .elf file in ./target/[your json spec file name]/release. This will be the file that avrdude will flash to the microcontroller with the following command: avrdude -p [your microcontroller] -c [your programmer] -P [PORT] -b 115200 -Uflash:w:[your file].elf:e.
If you are using a jtag2updi programmer as I did, before running that command, go to this repo and replace the avrdude.conf file in the directory with your avrdude executable with the avrdude.conf file in that repo. If you have a linux os, you may need to navigate to the directory and run sudo nano ./avrdude.conf to edit the file.
Development
This section will discuss my process for developing this project and can also be seen as a super verbose, instructive changelog.
Version 0.2.0
I decided that it was probably a good idea to make a GPIO trait for the pins and that having to pass a reference to the peripherals each time the pins are written to was tedious, so I changed the Pin enum to a struct that stores a reference to the peripherals. I then just made sure that it still worked.
I then changed the delay and init_timer functions to avoid having store a timer and pass it at each delay. Instead, it passes a reference to the peripherals.
Version 0.1.0
This video was an excellent starting point for embedded work although the example chip used had an arm cortex processor, which has better support in Rust than AVR. From there, I got the device specification file for the attiny412 in the form of an atdf from this repo, and using atdf2svd, I converted it to an svd to use svd2rust to generate the PAC.
To note, I got the following error from atdf2svd, so the TCA0 peripheral cannot be used since atdf2svd has no support for representing the split mode of this peripheral as an svd.
Working with the PAC, I used panic-halt as my panic handler, but when I went to compile, I encountered a few errors. Firstly, since it uses avr-gcc as a linker, the following error came from avr-gcc:
I found that the reason for this was that, as stated in the Installing Dependencies section, the version of avr-gcc that I had from ubuntu's apt package manager was too old (version 5.4.0), and I found one that worked from this site.
Next I got the following error regarding eh_personality:
I found a couple solutions from this site. I first added the following to the cargo.toml file:
[profile.dev] panic = "abort" [profile.release] panic = "abort"
Later, I decided that it probably was not best to have the panic behavior as abort in cargo.toml but use panic-halt, so I changed to the other solution of adding the following eh_personality function:
#![feature(lang_items)] fn main() { #[lang = "eh_personality"] extern "C" fn eh_personality() {} }
Then, after compiling and uploading some test code, the LED lit up, but did not blink at the desired rate.
I realized that I had not checked the status of the RTC peripheral before writing to it, so after doing that, it worked.
I then started working on the HAL, and I moved the PAC into its own directory and renamed the lib.rs file to mod.rs. This, however, started getting me a lot of errors.
Now that the PAC wasn't at the root of the crate anymore, its paths got messed up, so I just replaced all the crate:: elements with either super:: or super::super:: depending on how far it had to go.
After doing that, I just wrote the abstractions that you can see in the documentation for this version, and it worked.