├── .cargo └── config ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── avr-atmega328p.json ├── initialize_memory.S ├── interrupt_vector.S ├── linker-script ├── rust-toolchain ├── simulate.gdbinit └── src └── main.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "avr-atmega328p.json" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | blink.hex 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ruduino"] 2 | path = ruduino 3 | url = git@github.com:avr-rust/ruduino.git 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "avr-mcu" 5 | version = "0.3.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "json 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "target-cpu-fetch 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "xmltree 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 12 | ] 13 | 14 | [[package]] 15 | name = "blink" 16 | version = "0.1.0" 17 | dependencies = [ 18 | "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 19 | "ruduino 0.2.4", 20 | ] 21 | 22 | [[package]] 23 | name = "futures" 24 | version = "0.3.5" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | dependencies = [ 27 | "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [[package]] 36 | name = "futures-channel" 37 | version = "0.3.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | dependencies = [ 40 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "futures-core" 46 | version = "0.3.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "futures-io" 51 | version = "0.3.5" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "futures-sink" 56 | version = "0.3.5" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "futures-task" 61 | version = "0.3.5" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | 64 | [[package]] 65 | name = "futures-util" 66 | version = "0.3.5" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "pin-project 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 74 | ] 75 | 76 | [[package]] 77 | name = "json" 78 | version = "0.12.4" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | 81 | [[package]] 82 | name = "lazy_static" 83 | version = "1.4.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "pin-project" 88 | version = "0.4.22" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | dependencies = [ 91 | "pin-project-internal 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)", 92 | ] 93 | 94 | [[package]] 95 | name = "pin-project-internal" 96 | version = "0.4.22" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | dependencies = [ 99 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 100 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", 102 | ] 103 | 104 | [[package]] 105 | name = "pin-utils" 106 | version = "0.1.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "proc-macro2" 111 | version = "1.0.18" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | ] 116 | 117 | [[package]] 118 | name = "quote" 119 | version = "1.0.7" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | dependencies = [ 122 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 123 | ] 124 | 125 | [[package]] 126 | name = "ruduino" 127 | version = "0.2.4" 128 | dependencies = [ 129 | "avr-mcu 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "target-cpu-fetch 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "target-cpu-macro 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "syn" 136 | version = "1.0.33" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | dependencies = [ 139 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "target-cpu-fetch" 146 | version = "0.1.1" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | dependencies = [ 149 | "json 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", 150 | ] 151 | 152 | [[package]] 153 | name = "target-cpu-macro" 154 | version = "0.1.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | dependencies = [ 157 | "json 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "target-cpu-fetch 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 159 | ] 160 | 161 | [[package]] 162 | name = "unicode-xid" 163 | version = "0.2.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | 166 | [[package]] 167 | name = "xml-rs" 168 | version = "0.8.3" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | 171 | [[package]] 172 | name = "xmltree" 173 | version = "0.10.1" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | dependencies = [ 176 | "xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", 177 | ] 178 | 179 | [metadata] 180 | "checksum avr-mcu 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4c72808d438e648fa04c2b5a655eb59fc202622f1fb446182dd4a363b389af16" 181 | "checksum futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" 182 | "checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 183 | "checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 184 | "checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" 185 | "checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 186 | "checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 187 | "checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 188 | "checksum json 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 189 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 190 | "checksum pin-project 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)" = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" 191 | "checksum pin-project-internal 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" 192 | "checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 193 | "checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 194 | "checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 195 | "checksum syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 196 | "checksum target-cpu-fetch 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b362c2489eca702c03a77e3ef0753de5a68d8e2314fc784a6dfbba5069e05def" 197 | "checksum target-cpu-macro 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "382a642e3ca83cf65746ff7e570bf330bd0a9a985753fdcf78a78cb2c9a24580" 198 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 199 | "checksum xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" 200 | "checksum xmltree 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f76badaccb0313f1f0cb6582c2973f2dd0620f9652eb7a5ff6ced0cc3ac922b3" 201 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blink" 3 | version = "0.1.0" 4 | authors = ["Jake Goulding "] 5 | edition = "2018" 6 | 7 | [profile.release] 8 | debug = true 9 | lto = true 10 | opt-level = "z" 11 | 12 | [dependencies] 13 | futures = { version = "0.3.5", default-features = false } 14 | ruduino = { path = "./ruduino", default-features = false } 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ELF:=target/avr-atmega328p/release/blink.elf 2 | HEX:=blink.hex 3 | SERIAL_PORT:=/dev/cu.usbmodem143201 4 | 5 | RUSTFLAGS:=-Zverify-llvm-ir 6 | 7 | all: ${ELF} 8 | 9 | .PHONY: ${ELF} 10 | ${ELF}: 11 | RUSTFLAGS=${RUSTFLAGS} cargo build -Z build-std=core --release 12 | 13 | # Download the ELF to the board 14 | .PHONY: program 15 | program: ${ELF} 16 | avrdude -p atmega328p -c arduino -P ${SERIAL_PORT} -D -U flash:w:$< 17 | 18 | .PHONY: connect-terminal 19 | connect-terminal: 20 | picocom ${SERIAL_PORT} 21 | 22 | .PHONY: simulate-avr 23 | simulate-avr: ${ELF} 24 | simavr \ 25 | -m atmega328p \ 26 | -f 16000000 \ 27 | -g \ 28 | -v -v -v -v -v -v -v \ 29 | $< 30 | 31 | .PHONY: simulate-gdb 32 | simulate-gdb: ${ELF} 33 | avr-gdb -x simulate.gdbinit -tui $< 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust and Cargo program for Arduino Uno 2 | 3 | Features: 4 | 5 | 1. Uses timer interrupts to blink the LED. 6 | 1. Echos back characters sent via the built-in serial device. 7 | 1. Echos back characters sent via the built-in serial device using futures. 8 | 9 | The code is written completely in Rust and a small amount of assembly! 10 | We avoid using any of the GCC startup files. 11 | 12 | See the [official blink example][blink], which makes use of some 13 | GCC-provided code. 14 | 15 | [blink]: https://github.com/avr-rust/blink 16 | 17 | ## High-level instructions 18 | 19 | 1. Follow the [official instructions][book] to install `avr-gcc` and 20 | `avrdude`. Additionally install picocom. For example, using 21 | Homebrew: 22 | 23 | ``` 24 | brew install picocom 25 | ``` 26 | 27 | `avr-gcc` is used as the linker, `avrdude` uploads the finished 28 | code, and picocom is used as the serial terminal. 29 | 30 | 1. Build the code: `make` 31 | 32 | 1. Upload the code: `make program` 33 | 34 | 1. Connect the serial terminal: `make connect-terminal` 35 | 36 | [book]: https://book.avr-rust.com/ 37 | [avr-rust]: https://github.com/avr-rust/rust 38 | -------------------------------------------------------------------------------- /avr-atmega328p.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "avr", 3 | "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8", 4 | "eh-frame-header": false, 5 | "env": "", 6 | "executables": true, 7 | "is-builtin": true, 8 | "linker-flavor": "gcc", 9 | "linker-is-gnu": true, 10 | "llvm-target": "avr-unknown-unknown", 11 | "os": "unknown", 12 | "pre-link-args": { 13 | "gcc": [ 14 | "-Wl,--as-needed" 15 | ] 16 | }, 17 | "target-c-int-width": "16", 18 | "target-endian": "little", 19 | "target-pointer-width": "16", 20 | "vendor": "unknown", 21 | 22 | "-- COMMENT --": "The following were added", 23 | 24 | "cpu": "atmega328p", 25 | "linker": "avr-gcc", 26 | "exe-suffix": ".elf", 27 | "pre-link-args": { 28 | "gcc": [ 29 | "-mmcu=atmega328p", 30 | "-nostartfiles", 31 | "-T", 32 | "./linker-script", 33 | "./interrupt_vector.S", 34 | "./initialize_memory.S" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /initialize_memory.S: -------------------------------------------------------------------------------- 1 | .section .text 2 | .globl __initialize_memory 3 | .type __initialize_memory, @function 4 | 5 | ;;; Does not handle the region pointer! 6 | __initialize_memory: 7 | 8 | __initialize_memory_init_data: 9 | ldi r30, lo8(__data_load_start) 10 | ldi r31, hi8(__data_load_start) ; Z 11 | ldi r24, lo8(__data_end) 12 | ldi r25, hi8(__data_end) 13 | ldi r26, lo8(__data_start) 14 | ldi r27, hi8(__data_start) ; X 15 | rjmp __initialize_memory_init_data_check 16 | 17 | __initialize_memory_init_data_copy: 18 | lpm r18, Z+ 19 | st X+, r18 20 | 21 | __initialize_memory_init_data_check: 22 | cp r26, r24 23 | cpc r27, r25 24 | brne __initialize_memory_init_data_copy 25 | 26 | __initialize_memory_init_bss: 27 | ldi r24, lo8(__bss_end) 28 | ldi r25, hi8(__bss_end) 29 | ldi r26, lo8(__data_end) ; X 30 | ldi r27, hi8(__data_end) 31 | ldi r18, 0x00 32 | rjmp __initialize_memory_init_bss_check 33 | 34 | __initialize_memory_init_bss_copy: 35 | st X+, r18 36 | 37 | __initialize_memory_init_bss_check: 38 | cp r26, r24 39 | cpc r27, r25 40 | brne __initialize_memory_init_bss_copy 41 | 42 | __initialize_memory_finish: 43 | ret 44 | -------------------------------------------------------------------------------- /interrupt_vector.S: -------------------------------------------------------------------------------- 1 | .section .ivr, "ax", @progbits 2 | .align 1 3 | 4 | ivr: 5 | jmp _ivr_reset 6 | jmp _ivr_irq0 7 | jmp _ivr_irq1 8 | jmp _ivr_pin_change_0 9 | jmp _ivr_pin_change_1 10 | jmp _ivr_pin_change_2 11 | jmp _ivr_watchdog_timer 12 | jmp _ivr_timer2_compare_a 13 | jmp _ivr_timer2_compare_b 14 | jmp _ivr_timer2_overflow 15 | jmp _ivr_timer1_capture 16 | jmp _ivr_timer1_compare_a 17 | jmp _ivr_timer1_compare_b 18 | jmp _ivr_timer1_overflow 19 | jmp _ivr_timer0_compare_a 20 | jmp _ivr_timer0_compare_b 21 | jmp _ivr_timer0_overflow 22 | jmp _ivr_spi_transfer_complete 23 | jmp _ivr_usart_rx_complete 24 | jmp _ivr_usart_udr_empty 25 | jmp _ivr_usart_tx_complete 26 | jmp _ivr_adc_conversion_complete 27 | jmp _ivr_eeprom_ready 28 | jmp _ivr_analog_comparator 29 | jmp _ivr_two_wire_serial_interface 30 | jmp _ivr_store_program_memory_ready 31 | 32 | _ivr_undefined: 33 | reti 34 | 35 | ;;; By default, start all interrupt handlers as doing a no-op 36 | ;;; return. Since these symbols are weakly linked, user code can 37 | ;;; override each one on demand. 38 | .macro _ivr_default name target=_ivr_undefined 39 | .weak \name 40 | .set \name, \target 41 | .endm 42 | 43 | _ivr_default _ivr_reset main ; Default to `main` here, no need for indirection 44 | _ivr_default _ivr_irq0 45 | _ivr_default _ivr_irq1 46 | _ivr_default _ivr_pin_change_0 47 | _ivr_default _ivr_pin_change_1 48 | _ivr_default _ivr_pin_change_2 49 | _ivr_default _ivr_watchdog_timer 50 | _ivr_default _ivr_timer2_compare_a 51 | _ivr_default _ivr_timer2_compare_b 52 | _ivr_default _ivr_timer2_overflow 53 | _ivr_default _ivr_timer1_capture 54 | _ivr_default _ivr_timer1_compare_a 55 | _ivr_default _ivr_timer1_compare_b 56 | _ivr_default _ivr_timer1_overflow 57 | _ivr_default _ivr_timer0_compare_a 58 | _ivr_default _ivr_timer0_compare_b 59 | _ivr_default _ivr_timer0_overflow 60 | _ivr_default _ivr_spi_transfer_complete 61 | _ivr_default _ivr_usart_rx_complete 62 | _ivr_default _ivr_usart_udr_empty 63 | _ivr_default _ivr_usart_tx_complete 64 | _ivr_default _ivr_adc_conversion_complete 65 | _ivr_default _ivr_eeprom_ready 66 | _ivr_default _ivr_analog_comparator 67 | _ivr_default _ivr_two_wire_serial_interface 68 | _ivr_default _ivr_store_program_memory_ready 69 | -------------------------------------------------------------------------------- /linker-script: -------------------------------------------------------------------------------- 1 | /* 2 | * This script is a work in progress 3 | * 4 | * Debugging the output with `avr-objdump -w -s -x -D output.elf` is very useful. 5 | */ 6 | 7 | /* TODO: Verify memory addresses and lengths */ 8 | MEMORY { 9 | text (rx) : ORIGIN = 0x000000, LENGTH = 32K 10 | registers (rw!x) : ORIGIN = 0x800000, LENGTH = 256 11 | data (rw!x) : ORIGIN = 0x800100, LENGTH = 2K 12 | } 13 | 14 | SECTIONS { 15 | .text : { 16 | /* 17 | * The interrupt vector routines *must* start at address 0x0000 18 | * 19 | * Preserve every symbol in the Interrupt Vector Routines table to 20 | * prevent them from being garbage collected. 21 | */ 22 | KEEP(* (.ivr)); 23 | 24 | /* The rest of our code */ 25 | * (.text* .progmem.data*); 26 | } >text 27 | 28 | /* Data initialized to a value */ 29 | .data : AT(ADDR(.text) + SIZEOF(.text)) { 30 | __data_start = .; 31 | * (.data* .rodata*); 32 | __data_end = .; 33 | } >data 34 | 35 | /* Data not initialized to a value */ 36 | /* Even possible in Rust? */ 37 | /* Yes: zero-initialized */ 38 | .bss : AT(ADDR(.data) + SIZEOF(.data)) { 39 | __bss_start = .; 40 | /* Can we avoid actually copying this in? */ 41 | * (.bss*); 42 | __bss_end = .; 43 | } >data 44 | 45 | /* Set up variables for initialization routines */ 46 | __data_load_start = LOADADDR(.data); 47 | __data_load_end = __data_load_start + SIZEOF(.data); 48 | } 49 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2020-07-24 2 | -------------------------------------------------------------------------------- /simulate.gdbinit: -------------------------------------------------------------------------------- 1 | target remote :1234 2 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(lang_items)] 2 | #![feature(llvm_asm)] 3 | #![feature(naked_functions)] 4 | #![feature(abi_avr_interrupt)] 5 | #![feature(never_type)] 6 | #![feature(generators, generator_trait)] 7 | #![no_std] 8 | #![no_main] 9 | 10 | use core::{prelude::v1::*, ptr}; 11 | use ruduino::{prelude::*, legacy::serial, cores::atmega328p, Register, Pin}; 12 | 13 | #[allow(unused_macros)] 14 | macro_rules! println { 15 | ($f: literal, $($arg:expr),*$(,)?) => { 16 | writeln!(crate::write::SERIAL, $f, $($arg,)*).unwrap(); 17 | }; 18 | } 19 | 20 | #[lang = "eh_personality"] 21 | extern "C" fn eh_personality() {} 22 | 23 | #[panic_handler] 24 | fn panic(_info: &core::panic::PanicInfo) -> ! { 25 | loop { 26 | write::strln("PANIC"); 27 | 28 | // if let Some(s) = info.payload().downcast_ref::<&str>() { 29 | // crate::write::str(" "); 30 | // crate::write::str(s); 31 | // } 32 | 33 | // if let Some(l) = info.location() { 34 | // crate::write::str(l.file()); 35 | // crate::write::str(" ("); 36 | // crate::write::slice_hex(&l.line().to_be_bytes()); 37 | // crate::write::str(", "); 38 | // crate::write::slice_hex(&l.column().to_be_bytes()); 39 | // crate::write::str(")"); 40 | // } 41 | // crate::strln(""); 42 | } 43 | } 44 | 45 | #[no_mangle] 46 | pub unsafe extern "avr-interrupt" fn _ivr_timer1_compare_a() { 47 | let prev_value = ptr::read_volatile(atmega328p::PORTB::ADDRESS); 48 | ptr::write_volatile(atmega328p::PORTB::ADDRESS, prev_value ^ atmega328p::port::B5::MASK); 49 | } 50 | 51 | #[no_mangle] 52 | pub unsafe extern "avr-interrupt" fn _ivr_usart_rx_complete() { 53 | fut::rx_interrupt_handler(); 54 | } 55 | 56 | #[no_mangle] 57 | pub unsafe extern "avr-interrupt" fn _ivr_usart_udr_empty() { 58 | fut::tx_empty_interrupt_handler(); 59 | } 60 | 61 | const CPU_FREQUENCY_HZ: u64 = 16_000_000; 62 | const CPU_REGISTER_BYTES: u16 = 256; 63 | const CPU_RAM_BYTES: u16 = 2048; 64 | const CPU_INITIAL_STACK_POINTER: u16 = CPU_REGISTER_BYTES + CPU_RAM_BYTES - 1; 65 | 66 | const DESIRED_HZ_TIM1: f64 = 2.0; 67 | const TIM1_PRESCALER: u64 = 1024; 68 | const INTERRUPT_EVERY_1_HZ_1024_PRESCALER: u16 = 69 | ((CPU_FREQUENCY_HZ as f64 / (DESIRED_HZ_TIM1 * TIM1_PRESCALER as f64)) as u64 - 1) as u16; 70 | 71 | // const DESIRED_HZ_TIM0: f64 = 30.0; 72 | // const TIM0_PRESCALER: u64 = 1024; 73 | // const INTERRUPT_EVERY_30_HZ_1024_PRESCALER: u8 = ((CPU_FREQUENCY_HZ as f64 / (DESIRED_HZ_TIM0 * TIM0_PRESCALER as f64)) as u64 - 1) as u8; 74 | 75 | const BAUD: u64 = 9600; 76 | const MYUBRR: u16 = (CPU_FREQUENCY_HZ / 16 / BAUD - 1) as u16; 77 | 78 | mod initialize { 79 | use core::ptr; 80 | 81 | extern "C" { 82 | #[naked] 83 | fn __initialize_memory(); 84 | 85 | #[link_name = "__data_start"] 86 | static mut DATA_START: u8; 87 | #[link_name = "__data_end"] 88 | static mut DATA_END: u8; 89 | #[link_name = "__data_load_start"] 90 | static mut DATA_LOAD_START: u8; 91 | 92 | #[link_name = "__bss_start"] 93 | static mut BSS_START: u8; 94 | #[link_name = "__bss_end"] 95 | static mut BSS_END: u8; 96 | } 97 | 98 | unsafe fn zero_out_bss() { 99 | let mut bss: *mut u8 = &mut BSS_START; 100 | let bss_end: *mut u8 = &mut BSS_END; 101 | 102 | while bss != bss_end { 103 | ptr::write_volatile(bss, 0); 104 | bss = bss.offset(1); 105 | } 106 | } 107 | 108 | unsafe fn load_data() { 109 | let mut data: *mut u8 = &mut DATA_START; 110 | let data_end: *mut u8 = &mut DATA_END; 111 | let mut data_load: *mut u8 = &mut DATA_LOAD_START; 112 | 113 | while data != data_end { 114 | let d; 115 | llvm_asm! {"lpm $0, $1+" 116 | : /* output */ "=r"(d), "=Z"(data_load) 117 | : /* input */ "Z"(data_load) 118 | : /* clobber */ 119 | : /* options */ 120 | }; 121 | ptr::write_volatile(data, d); 122 | data = data.offset(1); 123 | } 124 | } 125 | 126 | pub unsafe fn memory() { 127 | load_data(); 128 | zero_out_bss(); 129 | 130 | //__initialize_memory(); 131 | } 132 | 133 | #[allow(unused)] 134 | pub fn info() -> (usize, usize) { 135 | unsafe { 136 | let data_len = &DATA_END as *const u8 as usize - &DATA_START as *const u8 as usize; 137 | let bss_len = &BSS_END as *const u8 as usize - &BSS_START as *const u8 as usize; 138 | (data_len, bss_len) 139 | } 140 | } 141 | } 142 | 143 | #[no_mangle] 144 | pub extern "C" fn main() -> ! { 145 | use ruduino::modules::{timer::timer16, Timer16}; 146 | 147 | // TODO: we know interrupts are off, don't we? 148 | without_interrupts(|| { 149 | unsafe { 150 | // The ABI requires that r1 starts as zero 151 | llvm_asm!("eor r1, r1"); 152 | ptr::write_volatile(atmega328p::SP::ADDRESS, CPU_INITIAL_STACK_POINTER); 153 | initialize::memory(); 154 | } 155 | 156 | unsafe { 157 | // Configure all Port B pins as outputs 158 | ptr::write_volatile(atmega328p::DDRB::ADDRESS, 0xFF); 159 | // Turn on all Port B pins 160 | // ptr::write_volatile(PORTB, 0xFF); 161 | } 162 | 163 | // TODO: Why is this called timer16 when the interrupt calls it timer1 ? 164 | atmega328p::Timer16::setup() 165 | .waveform_generation_mode( 166 | timer16::WaveformGenerationMode::ClearOnTimerMatchOutputCompare, 167 | ) 168 | .clock_source(timer16::ClockSource::Prescale1024) 169 | .output_compare_1(Some(INTERRUPT_EVERY_1_HZ_1024_PRESCALER)) 170 | .configure(); 171 | 172 | serial::Serial::new(MYUBRR) 173 | .character_size(serial::CharacterSize::EightBits) 174 | .mode(serial::Mode::Asynchronous) 175 | .parity(serial::Parity::Disabled) 176 | .stop_bits(serial::StopBits::OneBit) 177 | .configure(); 178 | }); 179 | 180 | // undefined reference to `__udivmodhi4' 181 | // println!("Hello, {}", "world"); 182 | 183 | // serial::transmit(b'O'); 184 | // serial::transmit(b'K'); 185 | // serial::transmit(b'\r'); 186 | // serial::transmit(b'\n'); 187 | // serial::receive(); 188 | 189 | // busy_sleep_100ms(); 190 | 191 | // exercise::serial(); 192 | // exercise::reference(); 193 | // exercise::i32(); 194 | // exercise::generator(); 195 | // exercise::function_pointers(); 196 | // exercise::function_pointers_in_struct(); 197 | 198 | //fut::do_futures(); 199 | 200 | // spin_loop(); 201 | bracketed_echo(); 202 | } 203 | 204 | #[inline(never)] 205 | fn busy_sleep_100ms() { 206 | // Numbers from trial and error; measured at 102.8ms 207 | for _ in 0u8..5 { 208 | for _ in 0x00u8..0xFF { 209 | for _ in 0x00u8..0xFF { 210 | unsafe { llvm_asm!("NOP") }; 211 | } 212 | } 213 | } 214 | } 215 | 216 | #[allow(unused)] 217 | fn spin_loop() -> ! { 218 | loop { 219 | write::strln("....loooooooooping..."); 220 | for i in b'0'..=b'9' { 221 | serial::transmit(i); 222 | serial::transmit(b' '); 223 | } 224 | serial::transmit(b'\r'); 225 | serial::transmit(b'\n'); 226 | } 227 | } 228 | 229 | #[allow(unused)] 230 | fn bracketed_echo() -> ! { 231 | loop { 232 | if let Some(b) = serial::try_receive() { 233 | serial::transmit(b'>'); 234 | serial::transmit(b); 235 | serial::transmit(b'<'); 236 | } 237 | // forever! 238 | } 239 | } 240 | 241 | #[allow(unused)] 242 | mod exercise { 243 | use crate::write; 244 | use core::{fmt::Write, ptr}; 245 | use ruduino::legacy::serial; 246 | 247 | pub fn serial() { 248 | serial::transmit(b'A'); 249 | write::newline(); 250 | 251 | for i in 0..4 { 252 | let b = unsafe { *write::TO_HEX.get_unchecked(i) }; 253 | serial::transmit(b); 254 | } 255 | write::newline(); 256 | 257 | write::u8_hex(0x00); 258 | write::newline(); 259 | write::u8_hex(0x05); 260 | write::newline(); 261 | write::u8_hex(0x50); 262 | write::newline(); 263 | write::u8_hex(0xFF); 264 | write::newline(); 265 | 266 | write::slice_hex(&[0x00, 0x05, 0x50, 0xFF]); 267 | write::newline(); 268 | 269 | write::raw("raw"); 270 | 271 | // writeln!(write::SERIAL, "writeln!").unwrap(); 272 | // write::newline(); 273 | 274 | serial::transmit(b'Z'); 275 | write::newline(); 276 | } 277 | 278 | #[inline(never)] 279 | pub fn reference() -> ! { 280 | let mut on = false; 281 | let on = &mut on; 282 | loop { 283 | if *on { 284 | write::strln("off"); 285 | } else { 286 | write::strln("on"); 287 | } 288 | *on = !*on; 289 | } 290 | } 291 | 292 | #[inline(never)] 293 | pub fn i32() -> ! { 294 | let mut a: i32 = 0; 295 | unsafe { ptr::write_volatile(&mut a, 0x56_AB_CD_EF) }; 296 | let b = unsafe { ptr::read_volatile(&mut a) }; 297 | loop { 298 | write::slice_hex(&b.to_be_bytes()); 299 | crate::busy_sleep_100ms(); 300 | } 301 | } 302 | 303 | #[inline(never)] 304 | pub fn generator() -> ! { 305 | use core::ops::Generator; 306 | 307 | let gen = static move || { 308 | let mut on = false; 309 | let on = &mut on; 310 | loop { 311 | if *on { 312 | crate::write::strln("on"); 313 | } else { 314 | crate::write::strln("off"); 315 | } 316 | *on = !*on; 317 | yield; 318 | } 319 | }; 320 | 321 | futures::pin_mut!(gen); 322 | 323 | loop { 324 | match gen.as_mut().resume(()) { 325 | core::ops::GeneratorState::Yielded(()) => {} 326 | core::ops::GeneratorState::Complete(_) => unreachable!(), 327 | } 328 | } 329 | } 330 | 331 | // Prevent inlining 332 | macro_rules! defeat_optimizer { 333 | (let $name:ident $(: $typ:ty)? = $val:expr;) => { 334 | let mut $name $(: $typ)? = $val; 335 | let $name = unsafe { 336 | ptr::write_volatile(&mut $name, $val); 337 | ptr::read_volatile(&mut $name) 338 | }; 339 | } 340 | } 341 | 342 | #[inline(never)] 343 | pub fn function_pointers() -> ! { 344 | fn called(a: u8, b: u8) -> u8 { 345 | a + b 346 | } 347 | 348 | defeat_optimizer!{ 349 | let the_function: fn(u8, u8) -> u8 = called; 350 | } 351 | 352 | loop { 353 | let res = the_function(1, 2); 354 | if res == 3 { 355 | write::strln("OK"); 356 | } else { 357 | write::strln("BAD"); 358 | } 359 | } 360 | } 361 | 362 | #[inline(never)] 363 | pub fn function_pointers_in_struct() -> ! { 364 | fn add(a: u8, b: u8) -> u8 { 365 | a + b 366 | } 367 | 368 | // struct VTable { 369 | // call: fn(u8, u8) -> u8, 370 | // } 371 | 372 | // static VT_ADD: VTable = VTable { 373 | // call: add, 374 | // }; 375 | 376 | static ADD: fn(u8, u8) -> u8 = add; 377 | 378 | // defeat_optimizer! { 379 | // let vt_add = &VT_ADD; 380 | // } 381 | 382 | defeat_optimizer! { 383 | let addi = ADD; 384 | } 385 | 386 | loop { 387 | let res = addi(1, 2); 388 | 389 | if res == 3 { 390 | write::strln("OK"); 391 | } else { 392 | write::strln("BAD"); 393 | } 394 | } 395 | } 396 | } 397 | 398 | #[allow(unused)] 399 | mod write { 400 | use core::fmt::{self, Write}; 401 | use ruduino::legacy::serial; 402 | 403 | pub static TO_HEX: &[u8; 16] = b"0123456789ABCDEF"; 404 | pub const SERIAL: SuperSerial = SuperSerial(()); 405 | 406 | pub fn slice_hex(v: &[u8]) { 407 | for &b in v { 408 | u8_hex(b); 409 | } 410 | } 411 | 412 | pub fn u8_hex(v: u8) { 413 | let top_idx = (v >> 4) & 0b1111; 414 | let bot_idx = (v >> 0) & 0b1111; 415 | 416 | let top = unsafe { *TO_HEX.get_unchecked(top_idx as usize) }; 417 | let bot = unsafe { *TO_HEX.get_unchecked(bot_idx as usize) }; 418 | 419 | // Useful for avoiding global variables 420 | 421 | // let c = |v| match v { 422 | // 0..=9 => b'0' + v, 423 | // _ => v - 10 + b'A', 424 | // }; 425 | // let top = c(top_idx); 426 | // let bot = c(bot_idx); 427 | 428 | serial::transmit(top); 429 | serial::transmit(bot); 430 | } 431 | 432 | pub fn newline() { 433 | serial::transmit(b'\r'); 434 | serial::transmit(b'\n'); 435 | } 436 | 437 | pub fn raw(s: &str) { 438 | for b in s.bytes() { 439 | serial::transmit(b); 440 | } 441 | newline(); 442 | } 443 | 444 | pub fn str(s: &str) { 445 | SERIAL.write_str(s).unwrap(); 446 | } 447 | 448 | pub fn strln(s: &str) { 449 | SERIAL.write_str(s).unwrap(); 450 | SERIAL.write_str("\r\n").unwrap(); 451 | } 452 | 453 | pub struct SuperSerial(()); 454 | 455 | impl fmt::Write for SuperSerial { 456 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 457 | for b in s.bytes() { 458 | serial::transmit(b); 459 | } 460 | Ok(()) 461 | } 462 | } 463 | } 464 | 465 | mod fut { 466 | use core::{ 467 | future::Future, 468 | pin::Pin, 469 | ptr, 470 | task::{Context, Poll, Waker}, 471 | }; 472 | use ruduino::{legacy::serial, cores::atmega328p, Register, RegisterBits}; 473 | 474 | // ruduino doesn't appear to use volatile yet... ? 475 | fn set_bit>(bit: RegisterBits) { 476 | unsafe { 477 | let old = ptr::read_volatile(R::ADDRESS); 478 | let new = old | u8::from(bit); 479 | ptr::write_volatile(R::ADDRESS, new); 480 | } 481 | } 482 | 483 | // ruduino doesn't appear to use volatile yet... ? 484 | fn unset_bit>(bit: RegisterBits) { 485 | unsafe { 486 | let old = ptr::read_volatile(R::ADDRESS); 487 | let new = old ^ u8::from(bit); 488 | ptr::write_volatile(R::ADDRESS, new); 489 | } 490 | } 491 | 492 | // Nemo157: 493 | // 494 | // I’m pretty sure that implementation is unsound for spurious 495 | // wakeups 496 | // 497 | // You could poll the future once, set the waker and enable the 498 | // interrupt, then poll the future again and have the interrupt 499 | // trigger during setting the waker again 500 | // 501 | // If the interrupt occurs mid-write to TX_WAKER then you will be 502 | // creating an &mut Option for .take() during that 503 | // concurrent modification 504 | 505 | #[allow(unused)] 506 | struct Serial; 507 | 508 | #[allow(unused)] 509 | impl Serial { 510 | // TODO: Maybe take a slice instead? 511 | fn tx(&self, byte: u8) -> SerialTx { 512 | SerialTx(byte) 513 | } 514 | 515 | // TODO: Maybe take a slice instead? 516 | fn rx<'a>(&self, byte: &'a mut u8) -> SerialRx<'a> { 517 | SerialRx(byte) 518 | } 519 | } 520 | 521 | struct SerialRx<'a>(&'a mut u8); 522 | 523 | static mut RX_WAKER: Option = None; 524 | 525 | #[inline(always)] 526 | pub fn rx_interrupt_handler() { 527 | // Safety: 528 | // We are on a single-threaded CPU, so static mutable shoudn't matter. 529 | unsafe { 530 | if let Some(waker) = RX_WAKER.take() { 531 | // Notify our waker to poll the future again 532 | waker.wake(); 533 | 534 | // We must either read from the buffer or disable the 535 | // interrupt to prevent re-invoking the interrupt 536 | // handler immediately. 537 | disable_serial_rx_interrupt(); 538 | } 539 | } 540 | } 541 | 542 | fn enable_serial_rx_interrupt() { 543 | set_bit(atmega328p::UCSR0B::RXCIE0); 544 | } 545 | 546 | fn disable_serial_rx_interrupt() { 547 | unset_bit(atmega328p::UCSR0B::RXCIE0); 548 | } 549 | 550 | impl<'a> Future for SerialRx<'a> { 551 | type Output = (); 552 | 553 | fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { 554 | match serial::try_receive() { 555 | Some(v) => { 556 | *Pin::get_mut(self).0 = v; 557 | Poll::Ready(()) 558 | } 559 | None => { 560 | // Safety: 561 | // We are on a single-threaded CPU, so static mutable shoudn't matter. 562 | unsafe { 563 | RX_WAKER = Some(ctx.waker().clone()); 564 | } 565 | enable_serial_rx_interrupt(); 566 | 567 | Poll::Pending 568 | } 569 | } 570 | } 571 | } 572 | 573 | struct SerialTx(u8); 574 | 575 | static mut TX_WAKER: Option = None; 576 | 577 | #[inline(always)] 578 | pub fn tx_empty_interrupt_handler() { 579 | // Safety: 580 | // We are on a single-threaded CPU, so static mutable shoudn't matter. 581 | 582 | unsafe { 583 | if let Some(waker) = TX_WAKER.take() { 584 | // Notify our waker to poll the future again 585 | waker.wake(); 586 | 587 | // We must either write to the buffer or disable the 588 | // interrupt to prevent re-invoking the interrupt 589 | // handler immediately. 590 | disable_serial_tx_empty_interrupt(); 591 | } 592 | } 593 | } 594 | 595 | fn enable_serial_tx_empty_interrupt() { 596 | set_bit(atmega328p::UCSR0B::UDRIE0); 597 | } 598 | 599 | fn disable_serial_tx_empty_interrupt() { 600 | unset_bit(atmega328p::UCSR0B::UDRIE0); 601 | } 602 | 603 | impl Future for SerialTx { 604 | type Output = (); 605 | 606 | fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { 607 | match serial::try_transmit(self.0) { 608 | Ok(()) => Poll::Ready(()), 609 | Err(()) => { 610 | // Safety: 611 | // We are on a single-threaded CPU, so static mutable shoudn't matter. 612 | unsafe { 613 | TX_WAKER = Some(ctx.waker().clone()); 614 | } 615 | enable_serial_tx_empty_interrupt(); 616 | 617 | Poll::Pending 618 | } 619 | } 620 | } 621 | } 622 | 623 | async fn bracketed_echo() -> ! { 624 | loop { 625 | let mut buf = 0; 626 | Serial.rx(&mut buf).await; 627 | 628 | Serial.tx(b'>').await; 629 | Serial.tx(buf).await; 630 | Serial.tx(b'<').await; 631 | } 632 | } 633 | 634 | #[allow(unused)] 635 | #[inline(never)] 636 | pub fn do_futures() -> ! { 637 | executor::block_on(bracketed_echo()) 638 | } 639 | 640 | mod executor { 641 | use core::{ 642 | future::Future, 643 | ptr, 644 | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, 645 | }; 646 | 647 | pub fn block_on(f: F) -> F::Output 648 | where 649 | F: Future, 650 | { 651 | let waker = my_waker(); 652 | let mut context = Context::from_waker(&waker); 653 | 654 | futures::pin_mut!(f); 655 | 656 | loop { 657 | match f.as_mut().poll(&mut context) { 658 | Poll::Ready(v) => return v, 659 | Poll::Pending => { 660 | // TODO: be more efficient? 661 | } 662 | } 663 | } 664 | } 665 | 666 | type WakerData = *const (); 667 | 668 | unsafe fn clone(_: WakerData) -> RawWaker { 669 | my_raw_waker() 670 | } 671 | unsafe fn wake(_: WakerData) {} 672 | unsafe fn wake_by_ref(_: WakerData) {} 673 | unsafe fn drop(_: WakerData) {} 674 | 675 | static MY_VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); 676 | 677 | fn my_raw_waker() -> RawWaker { 678 | RawWaker::new(ptr::null(), &MY_VTABLE) 679 | } 680 | 681 | fn my_waker() -> Waker { 682 | unsafe { Waker::from_raw(my_raw_waker()) } 683 | } 684 | } 685 | } 686 | 687 | #[no_mangle] 688 | extern "C" fn abort() -> ! { 689 | loop { 690 | write::strln("ABORT"); 691 | } 692 | } 693 | 694 | #[no_mangle] 695 | extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 { 696 | unsafe { 697 | for i in 0..n { 698 | *dst.add(i) = *src.add(i); 699 | } 700 | } 701 | dst 702 | } 703 | 704 | #[no_mangle] 705 | extern "C" fn __sync_lock_test_and_set_1(ptr: *mut u8, desired: u8) -> u8 { 706 | without_interrupts(|| unsafe { 707 | let old = *ptr; 708 | *ptr = desired; 709 | old 710 | }) 711 | } 712 | --------------------------------------------------------------------------------