Compare commits

...

286 Commits
0.0 ... master

Author SHA1 Message Date
Astro 2773ba47fe tec: init freq pins for 1 MHz switching 2019-11-14 01:59:57 +01:00
Astro 13771bf770 tec: fix wrong SHDN pins 2019-11-13 20:13:57 +01:00
Astro 02d2403547 tec: setup iset_width 2019-11-13 20:12:25 +01:00
Astro ecdebe76bc tec: keep shdn off until first use 2019-11-13 20:01:17 +01:00
Astro 6041e41716 Carto.toml: update dependencies 2019-11-13 19:50:14 +01:00
Astro 9b1a0696ab Cargo.toml: update smoltcp git 2019-11-13 19:23:49 +01:00
Astro d360ec6dce shell.nix: cd in shellHook 2019-11-10 23:01:22 +01:00
Astro 7efc95941b default.nix: update rust nightly 2019-11-10 23:01:10 +01:00
Astro 7efd42715e tec: enable GPIO pins for TEC SHDN 2019-11-10 22:27:20 +01:00
Astro aafb733209 default.nix: update cargoSha256 2019-10-30 21:06:42 +01:00
Astro bb35f91a57 build.rs: delint 2019-10-30 19:55:15 +01:00
Astro 225be7b911 upgrade to current rust + smoltcp 2019-10-30 19:53:35 +01:00
Astro c2aa0e2989 main: report pwm_width 2019-10-03 01:35:52 +02:00
Astro cc5c21e088 main: use temperature as PID input 2019-10-03 01:35:37 +02:00
Astro 420be00407 main: fix DEFAULT_PID_PARAMETERS 2019-10-03 01:34:53 +02:00
Astro 2010b4fe10 main: reset PID after parameter change 2019-10-03 01:33:49 +02:00
Astro c8d31c7b0d tec: add pwm default setup 2019-10-03 01:33:21 +02:00
Astro 4ac0f7b171 pid: add test 2019-10-03 00:44:36 +02:00
Astro 21615819f6 main: improve tcp output 2019-10-02 22:33:23 +02:00
Astro 59d3fde32e main: fix DEFAULT_SH_PARAMETERS 2019-10-02 22:21:50 +02:00
Astro 86b5841119 default.nix: run tests 2019-10-02 21:30:44 +02:00
Astro e1af02f77a Cargo.toml: bump libm dependency 2019-10-02 21:28:37 +02:00
Astro 53ff7e70ca main: use steinhart-hart equation for temperature conversion 2019-10-02 21:28:14 +02:00
Astro 83117db8c5 ad7172: perform data sign checks 2019-10-02 21:27:35 +02:00
Astro 12c2be0a03 command_parser: shorten to parallel_r, add tests 2019-10-02 21:25:06 +02:00
Astro 8611cc1c79 add configurable Steinhart-Hart equation 2019-10-02 19:55:33 +02:00
Astro 2992688184 Cargo.toml: update smoltcp to 0.5.0 2019-09-30 21:57:13 +02:00
Astro 7f32591441 default.nix: pin rust-nightly, build firmware 2019-09-30 21:54:44 +02:00
Astro 9486946d06 main: improve command resonses 2019-09-24 01:57:52 +02:00
Astro ef17e1c4ff README: describe all the commands 2019-09-24 01:55:19 +02:00
Astro b508129c37 tec: make more brief 2019-09-24 01:45:51 +02:00
Astro fca427cb5a show pwm config of each tec channel 2019-09-24 01:45:11 +02:00
Astro 18e3e95615 group 4 PWM channels into Tec, add commands to configure them 2019-09-24 01:33:23 +02:00
Astro a82ffadb35 add PWM abstraction for TEC control 2019-09-23 16:50:43 +02:00
Astro 8d01ca8d20 rm unused test dependency 2019-09-19 15:38:00 +02:00
Astro f3664f01be command_parser: fix float parsing 2019-09-19 15:28:11 +02:00
Astro 6f36c682cd command_parser: add tests
Run with: cargo test --target=x86_64-unknown-linux-gnu
2019-09-19 14:56:34 +02:00
Astro 35dfba99e1 style 2019-09-19 03:29:06 +02:00
Astro 63aa2347b7 add remote postfilter configuration 2019-09-19 03:28:47 +02:00
Astro 87de8b7859 style 2019-09-19 02:18:37 +02:00
Astro e126bc0fe1 command_parser: parameterize `pid`, `pwm` with channel `<0/1>` 2019-09-19 02:17:54 +02:00
Astro 1514131fa3 ad7172: fix doc
Closes Gitea issue #1
2019-09-19 01:00:49 +02:00
Astro 6d4676a72a command_parser: fix pid 2019-09-19 00:59:16 +02:00
Astro f64e4fe2f3 main: don't log expected failure, log MAC to semihosting 2019-09-19 00:49:41 +02:00
Astro 2d2e7e80e0 ad7172: rm unused FiltCon odc setting 2019-09-19 00:49:26 +02:00
Astro 2ca06e023b command_parser: use combinators, allow trailing whitespace 2019-09-19 00:46:50 +02:00
Astro 4e5de7831b command_parser: fix non-optional whitespace after `report` 2019-09-18 23:25:40 +02:00
Astro db1788bafb ad7172: fix bits in regs::setup_con 2019-09-18 22:45:29 +02:00
Astro ba485cab16 README: link rustup 2019-09-18 01:53:19 +02:00
Astro ee8f8e87c3 README: build instructions 2019-09-18 00:29:29 +02:00
Astro f048026d21 update dependency lexical-core 2019-09-17 19:22:23 +02:00
Astro 42a9b89db1 let pid control pwm 2019-09-17 19:09:47 +02:00
Astro 393c276bda document commands 2019-09-17 01:46:29 +02:00
Astro 4c00548646 main: revert resampling, report per-channel 2019-09-17 01:42:50 +02:00
Astro a7ee2107ea command_parser: rm outdated TODO note 2019-09-17 01:38:12 +02:00
Astro 4587406d44 ad7172: setup adc 2019-09-17 00:13:46 +02:00
Astro 7e51585aa9 command_parser: swap btoi for lexical-core to parse floats too 2019-09-16 23:41:22 +02:00
Astro d4428b7fdc ad7172: rename reg bit bi_unipolar to bipolar 2019-09-16 23:28:28 +02:00
Astro ee4d24de6a delint 2019-09-14 21:40:49 +02:00
Astro b969f5c057 command_parser: complete to all PidParameter tokens 2019-09-14 21:35:06 +02:00
Astro 700ab47f0e improve output formatting 2019-09-14 21:23:45 +02:00
Astro b6af43feda control the pid 2019-09-14 21:11:26 +02:00
Astro 426be0d5f1 main: remove allocator 2019-09-14 19:09:38 +02:00
Astro 5c58c4370d command_parser: switch from logos to nom 2019-09-14 19:03:52 +02:00
Astro 328f6921fa README: init 2019-09-14 16:59:21 +02:00
Astro 8163d083b9 main: print panic info with semihosting 2019-09-14 16:52:35 +02:00
Astro 1395e8b410 add support for manipulating pwm duty cycle 2019-09-14 03:09:07 +02:00
Astro 5e0f55647a command_parser: rm unused tokens 2019-09-14 02:35:12 +02:00
Astro 5ef8d6a747 main: rework reporting 2019-09-14 02:33:56 +02:00
Astro ff3a793c19 ad7172: restrict macro input to remove cast 2019-09-14 02:19:18 +02:00
Astro 07dcc608bc ad7172: setup_channel() fixes 2019-09-14 02:18:47 +02:00
Astro 957f92d177 main: fight jitter 2019-09-14 02:18:33 +02:00
Astro f8dd7d1912 main: setup thermostat v1 channels, report avgs at 10hz 2019-09-14 01:36:47 +02:00
Astro c50e1c7766 remove too detailed adc control 2019-09-14 00:48:26 +02:00
Astro 76e30c0f7c some too detailed adc config 2019-09-14 00:46:48 +02:00
Astro f2dcb8b08d command_parser: unnest the grammar definition 2019-09-13 01:14:14 +02:00
Astro 3fd1b2265d command_parser: channel setup 2019-09-13 01:04:59 +02:00
Astro edb0401838 add a very small allocator 2019-09-12 16:12:03 +02:00
Astro 44f5a8338c command_parser: s/CommandShow/ShowCommand/, delint 2019-09-11 00:23:15 +02:00
Astro 0dcd35c9f2 add session 2019-09-10 23:38:09 +02:00
Astro 7f95f01711 ad7172::regs: add Offset, Gain, docs, fix FiltCon address 2019-09-10 16:21:51 +02:00
Astro 3ef317f00d ad7172: add more regs 2019-09-09 16:20:15 +02:00
Astro 8b2cc15d7d ad7172: break out mods adc, checksum, regs 2019-09-08 21:41:59 +02:00
Astro 98a5788770 ad7172: add Channel, Input 2019-09-08 21:30:26 +02:00
Astro 4e518e88ee main: measure read intervals 2019-09-08 02:43:55 +02:00
Astro 25dc3fb70c systick: implement nanosecond precision 2019-09-08 02:34:59 +02:00
Astro 4249addba2 systick: use mutex 2019-09-08 02:13:02 +02:00
Astro 225f3754a1 move systick under board/ 2019-09-08 01:54:51 +02:00
Astro e5529a8b94 add systick interrupt for timekeeping 2019-09-08 01:24:20 +02:00
Astro 152bc7b98b main: use 10x nop delay for softspi 2019-09-08 00:47:41 +02:00
Astro 3bf1010969 ad7172: implement crc checksumming 2019-09-08 00:47:14 +02:00
Astro baab9b2d3f board: move softspi input read before high flank 2019-09-08 00:44:40 +02:00
Astro f7af12adf5 ad7172: refactor and add xor support 2019-09-08 00:22:42 +02:00
Astro 0697914182 upgrade to rust edition 2018 2019-09-05 23:34:48 +02:00
Astro c5dfaf0ee2 delint 2019-09-02 00:01:18 +02:00
Astro 3f7da6e328 ad7172: rm debug 2019-09-02 00:00:50 +02:00
Astro f94d915328 identify adc 2019-09-01 23:56:45 +02:00
Astro ea395460c8 add cortex-m-semihosting 2019-09-01 23:56:27 +02:00
Astro 01a9decfa1 board: fix gpio initialization 2019-09-01 23:47:15 +02:00
Astro e947415242 board: refactor gpio to use indexed access 2019-09-01 22:57:35 +02:00
Astro 5ba09db517 board: wait gpio portb ready 2019-09-01 22:46:15 +02:00
Astro fad1050556 gpio: fix PE4 idx 2019-09-01 22:28:46 +02:00
Astro bf8c7fda88 gpio: fix masked_data 2019-09-01 22:05:26 +02:00
Astro f94173788e default.nix: update rustcSrc 2019-08-31 20:22:33 +02:00
Astro d5b7855c3b board: fix gpio input dir flag
softspi works and ad7172 now returns data.
2019-08-30 00:29:00 +02:00
Astro 1df35ef15f ad7172: reset 2019-08-30 00:12:16 +02:00
Astro 12a1c1ac07 ad7172: start with nss high 2019-08-30 00:11:23 +02:00
Astro 2b855c8ad9 ad7172: add identify() 2019-08-29 23:56:02 +02:00
Astro 574b96187a ad7172: doc, style 2019-08-29 23:55:45 +02:00
Astro 6e02b2c4f6 ad7172: fix comms rw flag 2019-08-29 23:54:44 +02:00
Astro f3831aba18 main: change IP address to 192.168.1.26 2019-08-29 22:40:14 +02:00
Astro 0eefebf3ed board: reset timers for clean initialization
raced into hardfaults without.
2019-08-29 22:39:08 +02:00
Astro 9c0f560367 board: bring up gpio port b 2019-08-29 22:17:33 +02:00
Astro 8b20118198 board: enable timers 2019-08-29 21:41:16 +02:00
Sebastien Bourdeauducq 1329c1567c strip more ionpak code 2019-08-21 17:31:51 +08:00
Sebastien Bourdeauducq 68e2b4634f adapt OpenOCD script 2019-08-21 17:00:13 +08:00
Sebastien Bourdeauducq e23e13fced add nix files 2019-08-21 16:59:58 +08:00
Astro 82b6e8e179 main: rm stale coda, poll adc, broadcast over tcp 2019-08-08 02:26:53 +02:00
Astro 057ddbdbf6 add ad7172 adc 2019-08-08 02:26:53 +02:00
Astro 4437a4195e delay: is unsafe 2019-08-08 02:26:53 +02:00
Astro 44f48f6e0f main: construct softspi 2019-08-08 02:26:53 +02:00
Astro 9c7ca0df87 add board::delay 2019-08-08 02:26:53 +02:00
Astro f04bbd8726 softspi: add SyncSoftSpi 2019-08-08 02:26:53 +02:00
Astro 4bf52b093e implement softspi 2019-08-08 02:26:53 +02:00
Sebastien Bourdeauducq 2fcae2cdee remove other ionpak artifacts 2019-07-31 13:16:38 +08:00
sb10q 3fe5aae04e remove ionpak logo 2019-07-31 13:15:56 +08:00
sb10q 9a9a9e0107 remove ionpak readme 2019-07-31 13:15:41 +08:00
Astro 5f3d674e24 add gpio abstraction 2019-07-30 18:25:35 +02:00
Astro e4478c3efd timer setup 2019-07-30 17:02:05 +02:00
Astro 2ac3485b30 dismantle ionpak, drive PWMs at fixed rate 2019-07-30 15:35:56 +02:00
whitequark 1a00f5cf1a Update README. 2019-02-06 18:54:18 +00:00
whitequark 517e531589 Update for Rust 1.32, and upgrade dependencies. 2019-02-06 09:19:28 +00:00
whitequark 89104a551c Update for latest nightly. 2018-08-28 19:57:17 +00:00
Sébastien Bourdeauducq 8164b75dfa
errata: spelling 2018-04-14 18:30:56 +08:00
a-shafir 9e1aa8b698 errata: add some rev2 changes 2018-04-14 18:29:55 +08:00
whitequark 941f602a20 Use the crc crate. 2018-03-27 10:16:11 +00:00
whitequark 6ffb157cb8 Add Cache-Control header for static assets.
This avoids wasting device cycles on retrieving the same data over
and over.
2018-03-27 09:36:16 +00:00
whitequark 2bdc483e15 Unbreak network configuration page. 2018-03-27 09:32:37 +00:00
whitequark 2d29078ba1 Remove unused import. 2018-03-27 09:17:57 +00:00
whitequark 8de311a34f Remove unnecessary unsafe blocks. 2018-03-27 09:17:34 +00:00
whitequark b13ef96bbe Fix RxRing::buf_release.
The original code was correct after all.
2018-03-27 09:13:11 +00:00
whitequark 308ad97586 Rewrite ethmac to split ownership into RX/TX halves. 2018-03-27 09:10:47 +00:00
whitequark 5a5596f7a2 Fix signature of panic_fmt even further. 2018-03-27 08:55:35 +00:00
whitequark dce70f352a Fix signature of panic_fmt. 2018-03-27 08:41:23 +00:00
whitequark f067d0fee9 Fix DMA writing to dangling pointers in ethmac. 2018-03-26 12:35:28 +00:00
whitequark 83629cac49 Update smoltcp. 2018-03-26 11:37:14 +00:00
whitequark 4cac825c41 Update firmware dependencies. 2018-03-26 11:33:00 +00:00
Sebastien Bourdeauducq e1d7924969 openocd: use find instead of hardcoded paths 2018-03-19 20:31:41 +08:00
Sebastien Bourdeauducq cbdd2c409c Revert "Revert the rx_buf_release() change in 8491394a."
This reverts commit 724221c643.
2018-03-19 15:39:41 +08:00
Sebastien Bourdeauducq 13aa02a318 add rev2 BoM 2018-03-09 15:35:34 +08:00
Sebastien Bourdeauducq 762ababe7f add rev2 paste gerber 2018-03-09 15:35:24 +08:00
Sebastien Bourdeauducq 8737557752 remove 1U box from BS vendor from shopping list 2018-03-08 14:10:27 +08:00
Sebastien Bourdeauducq 4641af7dcb add rev2 hardware design 2018-03-08 14:02:58 +08:00
Sebastien Bourdeauducq 7a2cf0a5e5 remove broken mechanical design 2018-03-08 13:51:30 +08:00
Sebastien Bourdeauducq 9a1d839e19 update errata 2018-02-13 18:26:13 +08:00
Sebastien Bourdeauducq cf2e3c251e add new pictures with enclosure 2018-02-12 22:55:32 +08:00
Sebastien Bourdeauducq 7b9594f81a update errata 2018-02-11 20:30:25 +08:00
whitequark dcb5321e82 Fix cmp::max/min mixup in 8491394a. 2018-01-29 07:43:25 +00:00
whitequark 724221c643 Revert the rx_buf_release() change in 8491394a. 2018-01-29 07:30:12 +00:00
whitequark c6887e3813 Update cargo-m-rt.
Required for compatibility with newer rustc nightlies.
2018-01-26 13:07:43 +00:00
whitequark 8491394a50 Update to newer smoltcp.
This also fixes a bug where RX descriptors would be first advanced
and then released.
2018-01-26 13:07:43 +00:00
Sebastien Bourdeauducq fe088d7bba update errata 2017-12-29 15:05:12 +08:00
Sebastien Bourdeauducq 4347ddc537 update errata 2017-09-28 17:30:28 +08:00
Sebastien Bourdeauducq 416ac30496 do not use GDB for loading firmware
GDB adds unnecessary steps and it is buggy.
2017-09-25 19:15:46 +08:00
Sebastien Bourdeauducq 7b10386907 update errata 2017-09-20 19:57:19 +08:00
Sebastien Bourdeauducq 34ae0901ae README: add note about firewall 2017-09-09 17:25:23 +08:00
Sebastien Bourdeauducq 30746d0565 update smoltcp 2017-09-09 17:25:07 +08:00
Sebastien Bourdeauducq 916e940780 update smoltcp 2017-09-05 17:26:23 +08:00
Sebastien Bourdeauducq 46d7d8bf99 document usage 2017-08-08 13:13:52 +08:00
Sebastien Bourdeauducq e6f3a65642 add JSON interface 2017-08-08 13:13:41 +08:00
Sebastien Bourdeauducq 9c64304cf2 support resetting settings using pushbutton 2017-08-08 12:19:05 +08:00
Sebastien Bourdeauducq 5c1cacbd38 support setting IP address via web interface 2017-08-08 11:05:09 +08:00
Sebastien Bourdeauducq 1a06b524d2 implement support for HTTP GET arguments 2017-08-08 11:04:38 +08:00
Sebastien Bourdeauducq e8174f0773 config fixes 2017-08-08 10:48:59 +08:00
Sebastien Bourdeauducq e5ea9a3918 add config module 2017-08-07 23:57:29 +08:00
Sebastien Bourdeauducq 5955c0f97d more eeprom cleanups, use u8 for buffer 2017-08-07 23:56:51 +08:00
Sebastien Bourdeauducq bd9082561c add missing file from previous commit 2017-08-07 20:03:18 +08:00
Sebastien Bourdeauducq 49cac15621 track firmware version and expose to HTTP 2017-08-07 17:54:25 +08:00
Sebastien Bourdeauducq 98256a0239 clean up index.html 2017-08-07 16:49:47 +08:00
Sebastien Bourdeauducq 3059720430 configure Ethernet LED 2017-08-07 16:36:21 +08:00
Sebastien Bourdeauducq d812932732 cleanup and integrate EEPROM driver 2017-08-07 16:13:29 +08:00
Alexander Shafir f94b50e9ab add EEPROM driver 2017-08-07 12:27:59 +08:00
Sebastien Bourdeauducq dcd2a57aa4 get_time_ms: ADC sampling takes NSH+12 cycles and not NSH 2017-08-07 11:25:39 +08:00
Sebastien Bourdeauducq 5ef86b4516 detect invalid programmed MAC address 2017-08-07 11:18:19 +08:00
Alexander Shafir 115211c143 fix ethmac initialization 2017-08-07 11:18:00 +08:00
Sebastien Bourdeauducq e7bca6d0c7 fix TX buffer length in ethmac 2017-08-07 10:47:39 +08:00
Sebastien Bourdeauducq 93d0401b71 report measurements on HTTP page 2017-08-07 10:42:43 +08:00
Sebastien Bourdeauducq 5d4a223800 report target filament voltage 2017-08-07 10:37:24 +08:00
Sebastien Bourdeauducq c815d4d37f tweak cathode PID 2017-08-07 10:36:26 +08:00
Sebastien Bourdeauducq 9a4adb267d optimize electrometer averaging 2017-08-07 00:24:41 +08:00
Sebastien Bourdeauducq cccd6e52f6 remove GDB breakpoint at startup 2017-08-06 19:56:04 +08:00
Sebastien Bourdeauducq e676cb59bf work around ADC sample rate snafu 2017-08-06 19:53:16 +08:00
Sebastien Bourdeauducq f156c7c6d1 print panic messages on UART 2017-08-06 19:52:11 +08:00
Sebastien Bourdeauducq d18712d1be flashed MAC address doesn't work 2017-08-06 19:12:10 +08:00
Sebastien Bourdeauducq 68eb1cdbba start ADC after initialization, to avoid FIFO overflows 2017-08-06 19:06:47 +08:00
Sebastien Bourdeauducq 98b17fc574 update dependencies 2017-08-06 12:59:30 +08:00
Sebastien Bourdeauducq 1da96a2a4d convert ADC interrupt count to milliseconds 2017-08-06 11:37:24 +08:00
Sebastien Bourdeauducq 8247c8f5a5 refactor ethmac 2017-08-06 02:18:33 +08:00
Sebastien Bourdeauducq e8d6d84ac5 superficial ethmac cleanup 2017-08-05 16:24:22 +08:00
Sebastien Bourdeauducq 648b4da9da integrate ethmac/smoltcp (timestamp missing), add HTTP server 2017-08-05 15:51:54 +08:00
Sebastien Bourdeauducq 98f116e226 compile ethmac driver 2017-08-02 00:33:33 +08:00
Alexander Shafir 0ab3b6116e add ethmac driver 2017-07-31 13:37:00 +08:00
Sebastien Bourdeauducq c03b6a6fb7 errata: filament flyback output diode 2017-07-31 13:27:57 +08:00
Sebastien Bourdeauducq b3fd5568e5 add BoM 2017-07-24 15:55:39 +08:00
Sebastien Bourdeauducq 25f62e361c add Kicad design files 2017-07-24 15:55:30 +08:00
Sebastien Bourdeauducq 96489e2b92 add missing dimension on enclosure drawing 2017-07-24 15:49:27 +08:00
Sebastien Bourdeauducq 3503a9c6d3 add PDF enclosure silkscreen drawing 2017-07-22 19:56:42 +08:00
Sebastien Bourdeauducq 13f614f033 add mechanical drawings 2017-07-05 01:00:02 +08:00
Sebastien Bourdeauducq 23ba68bca9 Revert "README: use logo"
GitHub won't serve SVG images correctly for some reason.

This reverts commit d0889ed1f6.
2017-07-05 00:46:03 +08:00
Sebastien Bourdeauducq d0889ed1f6 README: use logo 2017-07-05 00:43:58 +08:00
Sebastien Bourdeauducq 2ac07d78d4 add logo 2017-07-05 00:41:25 +08:00
Sebastien Bourdeauducq 1444882679 update errata 2017-07-05 00:39:27 +08:00
Sebastien Bourdeauducq 914dc7f6c8 increase clock frequency, enable FPU correctly 2017-06-12 19:22:23 +08:00
Sebastien Bourdeauducq 52c123f215 raise threshold for switching to more sensitive IC range 2017-06-08 21:51:11 +08:00
Sebastien Bourdeauducq 5f19c2fdf5 clean up gauge parameters 2017-06-08 21:50:39 +08:00
Sebastien Bourdeauducq ef52ca7e2c enlarge README picture 2017-06-07 15:14:48 +08:00
Sebastien Bourdeauducq 0094a73336 add more pictures 2017-06-07 15:06:50 +08:00
Sebastien Bourdeauducq 2127da51a3 add shopping list 2017-06-06 18:48:30 +08:00
Sebastien Bourdeauducq fd513e553d README: add circular contact suggestion to connect to CF feedthroughs 2017-06-06 15:44:44 +08:00
Sebastien Bourdeauducq 6175c80f79 update Gerber files 2017-06-01 01:39:32 +08:00
Sebastien Bourdeauducq 536b46031e add Gerber files 2017-06-01 01:34:23 +08:00
Sebastien Bourdeauducq 46b806d66f fix R214 value 2017-05-31 13:06:24 +08:00
Sebastien Bourdeauducq f8cdbd6592 add value for C100 2017-05-23 16:37:52 +08:00
Sebastien Bourdeauducq 3bbdce1d43 update readme 2017-05-18 17:48:39 +08:00
Sebastien Bourdeauducq 7d7ea4228c update readme 2017-05-18 17:41:16 +08:00
Sebastien Bourdeauducq 680a00779e update readme, reorganize 2017-05-18 17:07:07 +08:00
Sebastien Bourdeauducq ebc341f28c add missing PID update 2017-05-17 00:08:02 +08:00
Sebastien Bourdeauducq 5f90960ce1 tweak emission PID parameters 2017-05-17 00:07:43 +08:00
Sebastien Bourdeauducq 5e13d30cc1 add parameters for chinese ion gauges 2017-05-17 00:06:36 +08:00
Sebastien Bourdeauducq 5ab0885a1f update errata 2017-05-17 00:05:25 +08:00
Sebastien Bourdeauducq 246839f153 autoselect IC range 2017-05-13 15:40:41 +08:00
Sebastien Bourdeauducq dc4534eb00 average more IC samples 2017-05-13 15:08:01 +08:00
Sebastien Bourdeauducq ba1482db43 adjust anode PID coefficients 2017-05-13 15:07:50 +08:00
Sebastien Bourdeauducq 9803388f49 update errata 2017-05-13 14:28:10 +08:00
Sebastien Bourdeauducq ad447cd5f9 use PID for FV, print all debug messages 2017-05-12 11:07:35 +08:00
Sebastien Bourdeauducq df335865b7 update errata 2017-05-12 11:05:37 +08:00
Sebastien Bourdeauducq 0ff950128c add license note 2017-05-11 23:15:12 +08:00
Sebastien Bourdeauducq 44d95973ca add electrometer, introduce *Status objects 2017-05-11 23:15:01 +08:00
Sebastien Bourdeauducq c676102b33 handle protection, print current/voltage values 2017-05-11 14:55:00 +08:00
Sebastien Bourdeauducq 1c516ca357 update FD_ADC_GAIN for new value of R234 2017-05-11 10:54:32 +08:00
Sébastien Bourdeauducq 3cbbd124b3 fix resistor number in errata 2017-05-10 18:29:03 +08:00
Sebastien Bourdeauducq fd507cb6fb average emission current samples, implement emission_ready 2017-05-10 00:29:52 +08:00
Sebastien Bourdeauducq 8975f8c240 compute emission current, filament voltage and bias voltage 2017-05-10 00:10:52 +08:00
Sebastien Bourdeauducq 9662570999 print message on protection latch 2017-05-10 00:10:16 +08:00
Sebastien Bourdeauducq 0d0c09b074 errata: glitches are ok, R234 should be changed 2017-05-09 20:13:39 +08:00
Sebastien Bourdeauducq f993e65fca fix elapsed counter 2017-05-09 19:08:16 +08:00
Sebastien Bourdeauducq caec6f1a3b compute FV and FBV 2017-05-09 19:07:59 +08:00
Sebastien Bourdeauducq c7f4dba53a remove unnecessary mut 2017-05-09 19:06:42 +08:00
Sebastien Bourdeauducq e4f513d444 reorganize 2017-05-09 15:58:06 +08:00
whitequark 8a49dfc980 Add UART debug port. 2017-05-09 05:16:00 +00:00
Sebastien Bourdeauducq 9fdce3ac4c speed up ADC 2017-05-08 23:47:05 +08:00
Sebastien Bourdeauducq 252b8eeb28 put ADC in continuous sample mode 2017-05-08 23:24:23 +08:00
Sebastien Bourdeauducq ecadb7c996 some PID tuning 2017-05-08 23:22:42 +08:00
whitequark b07cd31572 Raise ADC clock to 32 MHz (maximum). 2017-05-08 13:28:25 +00:00
Sebastien Bourdeauducq 592aee4735 update dependencies 2017-05-08 10:08:16 +08:00
whitequark ba37d13dae Enable FPU. 2017-05-07 17:32:53 +00:00
whitequark 3b7eb8bd9a Make ADC actually work. 2017-05-07 16:59:03 +00:00
whitequark 0ba8971aff Enable PLL at 320 MHz. 2017-05-07 16:28:03 +00:00
whitequark 8353ffbe98 Update errata. 2017-05-06 19:08:27 +00:00
whitequark d6bfba821e Unbreak ADC interrupt. 2017-05-06 16:00:01 +00:00
Sebastien Bourdeauducq 15c9bafb51 reset error latch at startup 2017-05-06 21:48:04 +08:00
Sebastien Bourdeauducq 0cb8d0c53d pid: use Option instead of NaN 2017-05-06 20:43:33 +08:00
Sebastien Bourdeauducq a39c95e276 HV PID control (untested) 2017-05-06 17:17:41 +08:00
Sebastien Bourdeauducq 8abeff05a1 Report error pin status 2017-05-06 12:33:38 +08:00
Sebastien Bourdeauducq 1069944ea2 style 2017-05-06 12:32:13 +08:00
Sebastien Bourdeauducq 4da283648b get ADC samples as ints 2017-05-06 12:30:56 +08:00
Sebastien Bourdeauducq 63c65448b2 fix gitignore 2017-05-06 12:29:42 +08:00
Sebastien Bourdeauducq 3393d9e3be add Ethernet TVS and error latch errata 2017-05-06 12:28:12 +08:00
whitequark 5431780700 Also set AFSEL bits for pins where AMSEL is set. 2017-05-05 12:05:19 +00:00
whitequark afda70c2a0 Move README.md back where it belongs. 2017-05-05 11:32:22 +00:00
whitequark 5a011ea410 Implement ADC readout. 2017-05-05 11:31:12 +00:00
whitequark 87e97c4894 Add schematics. 2017-05-05 09:25:01 +00:00
whitequark e17658c8db Move firmware to firmware/. 2017-05-05 09:23:08 +00:00
whitequark 8fa7ba9a8d Add missing wait for GPIO ready. 2017-05-05 07:53:12 +00:00
Sebastien Bourdeauducq 3bbccdce58 add relay errata 2017-05-05 00:21:00 +08:00
Sebastien Bourdeauducq 14b4d0a496 add GDT200 errata 2017-05-04 23:03:05 +08:00
Sebastien Bourdeauducq 67bee0ec06 set emission range 2017-05-04 23:00:22 +08:00
Sebastien Bourdeauducq 64b20aa335 set some FB potential 2017-05-04 22:07:43 +08:00
whitequark 4274d5e69b Add FBV_PWM pin handling. 2017-05-04 13:41:57 +00:00
Sebastien Bourdeauducq 070152c82f use PWM functions 2017-05-04 21:12:39 +08:00
Sebastien Bourdeauducq 2bb6be415d drive both flyback PWMs 2017-05-04 19:42:22 +08:00
Sebastien Bourdeauducq d458a337df LED access function 2017-05-04 18:59:43 +08:00
Sebastien Bourdeauducq 492b1e3668 hello world 2017-05-04 17:45:15 +08:00
Sebastien Bourdeauducq a214a1c6e0 work around picture cache problems 2017-05-04 16:25:32 +08:00
Sebastien Bourdeauducq 6af043a7ba smaller picture 2017-05-04 16:19:59 +08:00
Sebastien Bourdeauducq 2f2c53b28e improve readme appearance 2017-05-04 16:18:08 +08:00
32 changed files with 19012 additions and 37 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
firmware/target/
**/*.rs.bk

View File

@ -1,37 +0,0 @@
Ionpak
======
A modern, low-cost universal controller for ionization vacuum gauges.
![Prototype picture](https://raw.githubusercontent.com/m-labs/ionpak/master/proto_rev1_resized.jpg)
Flyback transformer construction
--------------------------------
TR300
*****
Use EPCOS coilformer B66208X1010T1. Wind 5 turns on the primary and spread them across the length of the coilformer - it is important that the air gap between the cores is covered by windings. Wind 70 turns on the secondary in multiple layers. As with all flyback transformers, the polarity of the windings is critical. Assemble with EPCOS cores B66317G500X127 and B66317GX127 (one half gapped core, one half ungapped core), and corresponding clips.
TR350
*****
Use EPCOS coilformer B66206W1110T1 and cores B66311G250X127 and B66311GX127. Both the primary and the secondary have 5 turns and must be wound together, interleaving the windings. The same remarks as for TR300 apply.
Errata
------
PCB rev 1
*********
* R307 needs more clearance from D400
* Pins 1 and 12 of U502 need pull-downs
* Pin 1 of U501 needs pull-up
* D203 reversed polarity
* R236 and R234 are swapped
* Q301 needs to be NPN, change to BC817
* increase R307 -> 3.3Kohm and increase R300 -> 33Kohm
* C201: oscillates at 0 and 1nF, stable at 100nF
* add clamp diodes to GND on op-amp outputs to ADC when op-amp has negative supply
* R214 -> 4.7k
* LM339PT is in TSSOP package. Change for SOIC P/N

15156
channel-rust-nightly.toml Normal file

File diff suppressed because it is too large Load Diff

62
default.nix Normal file
View File

@ -0,0 +1,62 @@
{ # Use master branch of the overlay by default
mozillaOverlay ? import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz),
rustManifest ? ./channel-rust-nightly.toml,
}:
let
pkgs = import <nixpkgs> { overlays = [ mozillaOverlay ]; };
in
with pkgs;
let
rustcSrc = pkgs.fetchgit {
url = https://github.com/rust-lang/rust.git;
# master of 2019-11-09
rev = "ac162c6abe34cdf965afc0389f6cefa79653c63b";
sha256 = "06c5gws1mrpr69z1gzs358zf7hcsg6ky8n4ha0vv2s9d9w93x1kj";
fetchSubmodules = true;
};
target = "thumbv7em-none-eabihf";
targets = [ target ];
rustChannelOfTargets = _channel: _date: targets:
(pkgs.lib.rustLib.fromManifestFile rustManifest {
inherit (pkgs) stdenv fetchurl patchelf;
}).rust.override { inherit targets; };
rust =
rustChannelOfTargets "nightly" null targets;
rustPlatform = recurseIntoAttrs (makeRustPlatform {
rustc = rust // { src = rustcSrc; };
cargo = rust;
});
gcc = pkgsCross.armv7l-hf-multiplatform.buildPackages.gcc;
xbuildRustPackage = attrs:
let
buildPkg = rustPlatform.buildRustPackage attrs;
in
buildPkg.overrideAttrs ({ name, nativeBuildInputs, ... }: {
nativeBuildInputs =
nativeBuildInputs ++ [ cargo-xbuild ];
buildPhase = ''
cargo xbuild --release --frozen
'';
XARGO_RUST_SRC = "${rustcSrc}/src";
installPhase = ''
mkdir $out
cp target/${target}/release/${name} $out/${name}.elf
'';
});
firmware = xbuildRustPackage {
name = "firmware";
src = ./firmware;
cargoSha256 = "0lf8h5g8sas36cxzqy0p65qqivnihh4gn4mkc1k210xp7niaymc5";
nativeBuildInputs = [
gcc
];
"CC_${target}" = "${gcc}/bin/armv7l-unknown-linux-gnueabihf-gcc";
RUST_COMPILER_RT_ROOT = "${rustcSrc}/src/llvm-project/compiler-rt";
checkPhase = ''
cargo test --target=${hostPlatform.config}
'';
};
in {
inherit pkgs rustPlatform rustcSrc gcc firmware;
}

8
firmware/.cargo/config Normal file
View File

@ -0,0 +1,8 @@
[target.thumbv7em-none-eabihf]
runner = "arm-none-eabi-gdb"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
target = "thumbv7em-none-eabihf"

421
firmware/Cargo.lock generated Normal file
View File

@ -0,0 +1,421 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aligned"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"as-slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "as-slice"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
"stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit_field"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "build_const"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "compiler_builtins"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cortex-m"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aligned 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cortex-m-rt"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cortex-m-rt-macros 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cortex-m-semihosting"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crc"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "embedded-hal"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "firmware"
version = "1.0.0"
dependencies = [
"bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"compiler_builtins 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
"cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cortex-m-rt 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smoltcp 0.5.0 (git+https://github.com/m-labs/smoltcp.git?rev=8eb01aca364aefe5f823d68d552d62c76c9be4a3)",
"tm4c129x 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "generic-array"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lexical-core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libm"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "managed"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nb"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nom"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "r0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "same-file"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "smoltcp"
version = "0.5.0"
source = "git+https://github.com/m-labs/smoltcp.git?rev=8eb01aca364aefe5f823d68d552d62c76c9be4a3#8eb01aca364aefe5f823d68d552d62c76c9be4a3"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "stable_deref_trait"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "static_assertions"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tm4c129x"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cortex-m-rt 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typenum"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vcell"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "volatile-register"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "walkdir"
version = "2.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-util"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aligned 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a316c7ea8e1e9ece54862c992def5a7ac14de9f5832b69d71760680efeeefa"
"checksum as-slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "293dac66b274fab06f95e7efb05ec439a6b70136081ea522d270bc351ae5bb27"
"checksum bare-metal 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
"checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum compiler_builtins 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d3c520d376a5b582ce93a7f881ba3fae1b72d9404aa9539af09d11e68b27e123"
"checksum cortex-m 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "145da2fc379bbd378ed425e75e1748214add9bbd800d4d5b77abb54ca423dbca"
"checksum cortex-m-rt 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "17805910e3ecf029bdbfcc42b7384d9e3d9e5626153fa810002c1ef9839338ac"
"checksum cortex-m-rt-macros 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2a6dc359ebb215c4924bffacfe46a8f02ef80fe2071bba1635a2ded42b40f936"
"checksum cortex-m-semihosting 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "113ef0ecffee2b62b58f9380f4469099b30e9f9cbee2804771b4203ba1762cfa"
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
"checksum embedded-hal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4908a155094da7723c2d60d617b820061e3b4efcc3d9e293d206a5a76c170b"
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
"checksum lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890"
"checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum nb 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc"
"checksum nom 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c618b63422da4401283884e6668d39f819a106ef51f5f59b81add00075da35ca"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum smoltcp 0.5.0 (git+https://github.com/m-labs/smoltcp.git?rev=8eb01aca364aefe5f823d68d552d62c76c9be4a3)" = "<none>"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92"
"checksum tm4c129x 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41ef3b3b339fa51167fcbe5fca426906bf9e7ac83333c3c69e94311a8586231d"
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum vcell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum volatile-register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286"
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

38
firmware/Cargo.toml Normal file
View File

@ -0,0 +1,38 @@
[package]
name = "firmware"
version = "1.0.0"
authors = ["whitequark <whitequark@whitequark.org>"]
edition = "2018"
[build-dependencies]
walkdir = "~2"
[dependencies]
libm = "0.1.4"
cortex-m = { version = "0.6", features = ["const-fn"] }
cortex-m-rt = "0.6"
crc = { version = "~1", default-features = false }
tm4c129x = { version = "0.9", features = ["rt"] }
embedded-hal = { version = "0.2", features = ["unproven"] }
nb = "0.1"
cortex-m-semihosting = "0.3"
byteorder = { version = "1.3", default-features = false }
bit_field = "0.10"
bare-metal = "0.2"
lexical-core = { version = "~0.6", default-features = false }
nom = { version = "~5", default-features = false }
[dependencies.smoltcp]
git = "https://github.com/m-labs/smoltcp.git"
rev = "8eb01aca364aefe5f823d68d552d62c76c9be4a3"
features = ["ethernet", "proto-ipv4", "socket-tcp"]
default-features = false
[dependencies.compiler_builtins]
version = "0.1"
default-features = false
features = ["mem", "no-lang-items", "c"]
[profile.release]
lto = true
debug = true

75
firmware/README.md Normal file
View File

@ -0,0 +1,75 @@
# Thermostat v1 prototype firmware
## Building
### On Debian-based systems
- install [rustup](https://rustup.rs/)
```shell
apt install gcc gcc-arm-none-eabi git-core
rustup toolchain install nightly
rustup update
rustup target add thumbv7em-none-eabihf --toolchain nightly
rustup default nightly
rustup component add rust-src
cargo install cargo-xbuild
git clone https://github.com/llvm/llvm-project.git
export RUST_COMPILER_RT_ROOT=`pwd`/llvm-project/compiler-rt
cd firmware && cargo xbuild --release
```
The built ELF file will be at `target/thumbv7em-none-eabihf/release/ionpak-firmware`
### Development build on NixOS
Requires NixOS 19.09 or later for cargo-xbuild.
```shell
nix-shell --run "cd firmware && cargo xbuild --release"
```
## Network
### Setup
Ethernet, IP: 192.168.1.26/24
Use telnet or netcat to connect to port 23/tcp (telnet)
### Reading ADC input
Set report mode to `once` to obtain the single next value. Report mode
will turn itself off after the next reading.
Set report mode to `continuous` for a continuous stream of input data.
The scope of this setting is per TCP session.
### Commands
| Syntax | Function |
| --- | --- |
| `report` | Show current input |
| `report mode` | Show current report mode |
| `report mode <off/on>` | Set report mode |
| `pwm <0/1> max_i_pos <width> <total>` | Set PWM duty cycle for **max_i_pos** to *width / total* |
| `pwm <0/1> max_i_neg <width> <total>` | Set PWM duty cycle for **max_i_neg** to *width / total* |
| `pwm <0/1> max_v <width> <total>` | Set PWM duty cycle for **max_v** to *width / total* |
| `pwm <0/1> <width> <total>` | Set PWM duty cycle for **i_set** to manual *width / total* |
| `pwm <0/1> pid` | Set PWM to be controlled by PID |
| `pid` | Show PID configuration |
| `pid <0/1> target <value>` | Set the PID controller target |
| `pid <0/1> kp <value>` | Set proportional gain |
| `pid <0/1> ki <value>` | Set integral gain |
| `pid <0/1> kd <value>` | Set differential gain |
| `pid <0/1> output_min <value>` | Set mininum output |
| `pid <0/1> output_max <value>` | Set maximum output |
| `pid <0/1> integral_min <value>` | Set integral lower bound |
| `pid <0/1> integral_max <value>` | Set integral upper bound |
| `s-h` | Show Steinhart-Hart equation parameters |
| `s-h <0/1> <a/b/c> <value>` | Set Steinhart-Hart parameter for a channel |
| `s-h <0/1> parallel_r <value>` | Set parallel resistance of the ADC |
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate |

22
firmware/build.rs Normal file
View File

@ -0,0 +1,22 @@
extern crate walkdir;
use std::env;
use std::io::Write;
use std::fs::File;
use std::path::PathBuf;
fn linker_script() {
// Put the linker script somewhere the linker can find it
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=memory.x");
}
fn main() {
linker_script();
}

7
firmware/memory.x Normal file
View File

@ -0,0 +1,7 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

9
firmware/openocd.cfg Normal file
View File

@ -0,0 +1,9 @@
source [find interface/stlink-v2.cfg]
transport select hla_swd
set CHIPNAME tm4c1294kcpd
set CPUTAPID 0x2ba01477
source [find target/stellaris.cfg]
program target/thumbv7em-none-eabihf/release/ionpak-firmware verify
reset
exit

201
firmware/src/ad7172/adc.rs Normal file
View File

@ -0,0 +1,201 @@
use embedded_hal::digital::v2::OutputPin;
use embedded_hal::blocking::spi::Transfer;
use super::checksum::{ChecksumMode, Checksum};
use super::AdcError;
use super::{
regs, regs::RegisterData,
Input, RefSource, PostFilter, DigitalFilterOrder,
};
/// AD7172-2 implementation
///
/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf)
pub struct Adc<SPI: Transfer<u8>, NSS: OutputPin> {
spi: SPI,
nss: NSS,
checksum_mode: ChecksumMode,
}
impl<SPI: Transfer<u8>, NSS: OutputPin> Adc<SPI, NSS> {
pub fn new(spi: SPI, mut nss: NSS) -> Result<Self, SPI::Error> {
let _ = nss.set_high();
let mut adc = Adc {
spi, nss,
checksum_mode: ChecksumMode::Off,
};
adc.reset()?;
Ok(adc)
}
/// `0x00DX` for AD7172-2
pub fn identify(&mut self) -> Result<u16, AdcError<SPI::Error>> {
self.read_reg(&regs::Id)
.map(|id| id.id())
}
pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), AdcError<SPI::Error>> {
// Cannot use update_reg() here because checksum_mode is
// updated between read_reg() and write_reg().
let mut ifmode = self.read_reg(&regs::IfMode)?;
ifmode.set_crc(mode);
self.checksum_mode = mode;
self.write_reg(&regs::IfMode, &mut ifmode)?;
Ok(())
}
pub fn set_sync_enable(&mut self, enable: bool) -> Result<(), AdcError<SPI::Error>> {
self.update_reg(&regs::GpioCon, |data| {
data.set_sync_en(enable);
})
}
pub fn setup_channel(
&mut self, index: u8, in_pos: Input, in_neg: Input
) -> Result<(), AdcError<SPI::Error>> {
self.update_reg(&regs::SetupCon { index }, |data| {
data.set_bipolar(false);
data.set_refbuf_pos(true);
data.set_refbuf_neg(true);
data.set_ainbuf_pos(true);
data.set_ainbuf_neg(true);
data.set_ref_sel(RefSource::External);
})?;
self.update_reg(&regs::FiltCon { index }, |data| {
data.set_enh_filt_en(true);
data.set_enh_filt(PostFilter::F16SPS);
data.set_order(DigitalFilterOrder::Sinc5Sinc1);
})?;
// let mut offset = <regs::Offset as regs::Register>::Data::empty();
// offset.set_offset(0);
// self.write_reg(&regs::Offset { index }, &mut offset);
self.update_reg(&regs::Channel { index }, |data| {
data.set_setup(index);
data.set_enabled(true);
data.set_a_in_pos(in_pos);
data.set_a_in_neg(in_neg);
})?;
Ok(())
}
pub fn get_postfilter(&mut self, index: u8) -> Result<Option<PostFilter>, AdcError<SPI::Error>> {
self.read_reg(&regs::FiltCon { index })
.map(|data| {
if data.enh_filt_en() {
Some(data.enh_filt())
} else {
None
}
})
}
pub fn set_postfilter(&mut self, index: u8, filter: Option<PostFilter>) -> Result<(), AdcError<SPI::Error>> {
self.update_reg(&regs::FiltCon { index }, |data| {
match filter {
None => data.set_enh_filt_en(false),
Some(filter) => {
data.set_enh_filt_en(true);
data.set_enh_filt(filter);
}
}
})
}
/// Returns the channel the data is from
pub fn data_ready(&mut self) -> Result<Option<u8>, AdcError<SPI::Error>> {
self.read_reg(&regs::Status)
.map(|status| {
if status.ready() {
Some(status.channel())
} else {
None
}
})
}
/// Get data
pub fn read_data(&mut self) -> Result<i32, AdcError<SPI::Error>> {
self.read_reg(&regs::Data)
.map(|data| data.data())
}
fn read_reg<R: regs::Register>(&mut self, reg: &R) -> Result<R::Data, AdcError<SPI::Error>> {
let mut reg_data = R::Data::empty();
let address = 0x40 | reg.address();
let mut checksum = Checksum::new(self.checksum_mode);
checksum.feed(address);
let checksum_out = checksum.result();
let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?;
for &mut b in reg_data.as_mut() {
checksum.feed(b);
}
let checksum_expected = checksum.result();
if checksum_expected != checksum_in {
return Err(AdcError::ChecksumMismatch(checksum_expected, checksum_in));
}
Ok(reg_data)
}
fn write_reg<R: regs::Register>(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), AdcError<SPI::Error>> {
let address = reg.address();
let mut checksum = Checksum::new(match self.checksum_mode {
ChecksumMode::Off => ChecksumMode::Off,
// write checksums are always crc
ChecksumMode::Xor => ChecksumMode::Crc,
ChecksumMode::Crc => ChecksumMode::Crc,
});
checksum.feed(address);
for &mut b in reg_data.as_mut() {
checksum.feed(b);
}
let checksum_out = checksum.result();
self.transfer(address, reg_data.as_mut(), checksum_out)?;
Ok(())
}
fn update_reg<R, F, A>(&mut self, reg: &R, f: F) -> Result<A, AdcError<SPI::Error>>
where
R: regs::Register,
F: FnOnce(&mut R::Data) -> A,
{
let mut reg_data = self.read_reg(reg)?;
let result = f(&mut reg_data);
self.write_reg(reg, &mut reg_data)?;
Ok(result)
}
pub fn reset(&mut self) -> Result<(), SPI::Error> {
let mut buf = [0xFFu8; 8];
let _ = self.nss.set_low();
let result = self.spi.transfer(&mut buf);
let _ = self.nss.set_high();
result?;
Ok(())
}
fn transfer<'w>(&mut self, addr: u8, reg_data: &'w mut [u8], checksum: Option<u8>) -> Result<Option<u8>, SPI::Error> {
let mut addr_buf = [addr];
let _ = self.nss.set_low();
let result = match self.spi.transfer(&mut addr_buf) {
Ok(_) => self.spi.transfer(reg_data),
Err(e) => Err(e),
};
let result = match (result, checksum) {
(Ok(_),None) =>
Ok(None),
(Ok(_), Some(checksum_out)) => {
let mut checksum_buf = [checksum_out; 1];
match self.spi.transfer(&mut checksum_buf) {
Ok(_) => Ok(Some(checksum_buf[0])),
Err(e) => Err(e),
}
}
(Err(e), _) =>
Err(e),
};
let _ = self.nss.set_high();
result
}
}

View File

@ -0,0 +1,54 @@
#[derive(Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum ChecksumMode {
Off = 0b00,
/// Seems much less reliable than `Crc`
Xor = 0b01,
Crc = 0b10,
}
impl From<u8> for ChecksumMode {
fn from(x: u8) -> Self {
match x {
0 => ChecksumMode::Off,
1 => ChecksumMode::Xor,
_ => ChecksumMode::Crc,
}
}
}
pub struct Checksum {
mode: ChecksumMode,
state: u8,
}
impl Checksum {
pub fn new(mode: ChecksumMode) -> Self {
Checksum { mode, state: 0 }
}
pub fn feed(&mut self, input: u8) {
match self.mode {
ChecksumMode::Off => {},
ChecksumMode::Xor => self.state ^= input,
ChecksumMode::Crc => {
for i in 0..8 {
let input_mask = 0x80 >> i;
self.state = (self.state << 1) ^
if ((self.state & 0x80) != 0) != ((input & input_mask) != 0) {
0x07 /* x8 + x2 + x + 1 */
} else {
0
};
}
}
}
}
pub fn result(&self) -> Option<u8> {
match self.mode {
ChecksumMode::Off => None,
_ => Some(self.state)
}
}
}

198
firmware/src/ad7172/mod.rs Normal file
View File

@ -0,0 +1,198 @@
use core::fmt;
pub mod regs;
mod checksum;
pub use checksum::ChecksumMode;
mod adc;
pub use adc::*;
#[derive(Clone, Debug, PartialEq)]
pub enum AdcError<SPI> {
SPI(SPI),
ChecksumMismatch(Option<u8>, Option<u8>),
}
impl<SPI> From<SPI> for AdcError<SPI> {
fn from(e: SPI) -> Self {
AdcError::SPI(e)
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum Input {
Ain0 = 0,
Ain1 = 1,
Ain2 = 2,
Ain3 = 3,
Ain4 = 4,
TemperaturePos = 17,
TemperatureNeg = 18,
AnalogSupplyPos = 19,
AnalogSupplyNeg = 20,
RefPos = 21,
RefNeg = 22,
Invalid = 0b11111,
}
impl From<u8> for Input {
fn from(x: u8) -> Self {
match x {
0 => Input::Ain0,
1 => Input::Ain1,
2 => Input::Ain2,
3 => Input::Ain3,
4 => Input::Ain4,
17 => Input::TemperaturePos,
18 => Input::TemperatureNeg,
19 => Input::AnalogSupplyPos,
20 => Input::AnalogSupplyNeg,
21 => Input::RefPos,
22 => Input::RefNeg,
_ => Input::Invalid,
}
}
}
impl fmt::Display for Input {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use Input::*;
match self {
Ain0 => "ain0",
Ain1 => "ain1",
Ain2 => "ain2",
Ain3 => "ain3",
Ain4 => "ain4",
TemperaturePos => "temperature+",
TemperatureNeg => "temperature-",
AnalogSupplyPos => "analogsupply+",
AnalogSupplyNeg => "analogsupply-",
RefPos => "ref+",
RefNeg => "ref-",
_ => "<INVALID>",
}.fmt(fmt)
}
}
/// Reference source for ADC conversion
#[repr(u8)]
pub enum RefSource {
/// External reference
External = 0b00,
/// Internal 2.5V reference
Internal = 0b10,
/// AVDD1 AVSS
Avdd1MinusAvss = 0b11,
Invalid = 0b01,
}
impl From<u8> for RefSource {
fn from(x: u8) -> Self {
match x {
0 => RefSource::External,
1 => RefSource::Internal,
2 => RefSource::Avdd1MinusAvss,
_ => RefSource::Invalid,
}
}
}
impl fmt::Display for RefSource {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use RefSource::*;
match self {
External => "external",
Internal => "internal",
Avdd1MinusAvss => "avdd1-avss",
_ => "<INVALID>",
}.fmt(fmt)
}
}
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum PostFilter {
/// 27 SPS, 47 dB rejection, 36.7 ms settling
F27SPS = 0b010,
/// 21.25 SPS, 62 dB rejection, 40 ms settling
F21SPS = 0b011,
/// 20 SPS, 86 dB rejection, 50 ms settling
F20SPS = 0b101,
/// 16.67 SPS, 92 dB rejection, 60 ms settling
F16SPS = 0b110,
Invalid = 0b111,
}
impl PostFilter {
pub const VALID_VALUES: &'static [Self] = &[
PostFilter::F27SPS,
PostFilter::F21SPS,
PostFilter::F20SPS,
PostFilter::F16SPS,
];
pub fn closest(rate: f32) -> Option<Self> {
/// (x - y).abs()
fn d(x: f32, y: f32) -> f32 {
if x >= y {
x - y
} else {
y - x
}
}
let mut best: Option<(f32, Self)> = None;
for value in Self::VALID_VALUES {
let error = d(rate, value.output_rate().unwrap());
let better = best
.map(|(best_error, _)| error < best_error)
.unwrap_or(true);
if better {
best = Some((error, *value));
}
}
best.map(|(_, best)| best)
}
/// Samples per Second
pub fn output_rate(&self) -> Option<f32> {
match self {
PostFilter::F27SPS => Some(27.0),
PostFilter::F21SPS => Some(21.25),
PostFilter::F20SPS => Some(20.0),
PostFilter::F16SPS => Some(16.67),
PostFilter::Invalid => None,
}
}
}
impl From<u8> for PostFilter {
fn from(x: u8) -> Self {
match x {
0b010 => PostFilter::F27SPS,
0b011 => PostFilter::F21SPS,
0b101 => PostFilter::F20SPS,
0b110 => PostFilter::F16SPS,
_ => PostFilter::Invalid,
}
}
}
#[repr(u8)]
pub enum DigitalFilterOrder {
Sinc5Sinc1 = 0b00,
Sinc3 = 0b11,
Invalid = 0b10,
}
impl From<u8> for DigitalFilterOrder {
fn from(x: u8) -> Self {
match x {
0b00 => DigitalFilterOrder::Sinc5Sinc1,
0b11 => DigitalFilterOrder::Sinc3,
_ => DigitalFilterOrder::Invalid,
}
}
}

260
firmware/src/ad7172/regs.rs Normal file
View File

@ -0,0 +1,260 @@
use byteorder::{BigEndian, ByteOrder};
use bit_field::BitField;
use super::*;
pub trait Register {
type Data: RegisterData;
fn address(&self) -> u8;
}
pub trait RegisterData {
fn empty() -> Self;
fn as_mut(&mut self) -> &mut [u8];
}
macro_rules! def_reg {
($Reg: ident, $reg: ident, $addr: expr, $size: expr) => {
/// AD7172 register
pub struct $Reg;
impl Register for $Reg {
/// Register contents
type Data = $reg::Data;
/// Register address
fn address(&self) -> u8 {
$addr
}
}
mod $reg {
/// Register contents
pub struct Data(pub [u8; $size]);
impl super::RegisterData for Data {
/// Generate zeroed register contents
fn empty() -> Self {
Data([0; $size])
}
/// Borrow for SPI transfer
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
}
};
($Reg: ident, u8, $reg: ident, $addr: expr, $size: expr) => {
pub struct $Reg { pub index: u8, }
impl Register for $Reg {
type Data = $reg::Data;
fn address(&self) -> u8 {
$addr + self.index
}
}
mod $reg {
pub struct Data(pub [u8; $size]);
impl super::RegisterData for Data {
fn empty() -> Self {
Data([0; $size])
}
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
}
}
}
macro_rules! reg_bit {
($getter: ident, $byte: expr, $bit: expr, $doc: expr) => {
#[allow(unused)]
#[doc = $doc]
pub fn $getter(&self) -> bool {
self.0[$byte].get_bit($bit)
}
};
($getter: ident, $setter: ident, $byte: expr, $bit: expr, $doc: expr) => {
#[allow(unused)]
#[doc = $doc]
pub fn $getter(&self) -> bool {
self.0[$byte].get_bit($bit)
}
#[allow(unused)]
#[doc = $doc]
pub fn $setter(&mut self, value: bool) {
self.0[$byte].set_bit($bit, value);
}
};
}
macro_rules! reg_bits {
($getter: ident, $byte: expr, $bits: expr, $doc: expr) => {
#[allow(unused)]
#[doc = $doc]
pub fn $getter(&self) -> u8 {
self.0[$byte].get_bits($bits)
}
};
($getter: ident, $setter: ident, $byte: expr, $bits: expr, $doc: expr) => {
#[allow(unused)]
#[doc = $doc]
pub fn $getter(&self) -> u8 {
self.0[$byte].get_bits($bits)
}
#[allow(unused)]
#[doc = $doc]
pub fn $setter(&mut self, value: u8) {
self.0[$byte].set_bits($bits, value);
}
};
($getter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => {
#[allow(unused)]
#[doc = $doc]
pub fn $getter(&self) -> $ty {
self.0[$byte].get_bits($bits) as $ty
}
};
($getter: ident, $setter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => {
#[allow(unused)]
#[doc = $doc]
pub fn $getter(&self) -> $ty {
self.0[$byte].get_bits($bits).into()
}
#[allow(unused)]
#[doc = $doc]
pub fn $setter(&mut self, value: $ty) {
self.0[$byte].set_bits($bits, value as u8);
}
};
}
def_reg!(Status, status, 0x00, 1);
impl status::Data {
/// Is there new data to read?
pub fn ready(&self) -> bool {
! self.not_ready()
}
reg_bit!(not_ready, 0, 7, "No data ready indicator");
reg_bits!(channel, 0, 0..=1, "Channel for which data is ready");
reg_bit!(adc_error, 0, 6, "ADC error");
reg_bit!(crc_error, 0, 5, "SPI CRC error");
reg_bit!(reg_error, 0,4, "Register error");
}
def_reg!(IfMode, if_mode, 0x02, 2);
impl if_mode::Data {
reg_bits!(crc, set_crc, 1, 2..=3, ChecksumMode, "SPI checksum mode");
}
def_reg!(Data, data, 0x04, 3);
impl data::Data {
pub fn data(&self) -> i32 {
let raw =
(u32::from(self.0[0]) << 16) |
(u32::from(self.0[1]) << 8) |
u32::from(self.0[2]);
if raw & 0x80_0000 != 0 {
((raw & 0x7F_FFFF) | 0x8000_0000) as i32
} else {
raw as i32
}
}
}
def_reg!(GpioCon, gpio_con, 0x06, 2);
impl gpio_con::Data {
reg_bit!(sync_en, set_sync_en, 0, 3, "Enables the SYNC/ERROR pin as a sync input");
}
def_reg!(Id, id, 0x07, 2);
impl id::Data {
pub fn id(&self) -> u16 {
BigEndian::read_u16(&self.0)
}
}
def_reg!(Channel, u8, channel, 0x10, 2);
impl channel::Data {
reg_bit!(enabled, set_enabled, 0, 7, "Channel enabled");
reg_bits!(setup, set_setup, 0, 4..=5, "Setup number");
/// Which input is connected to positive input of this channel
#[allow(unused)]
pub fn a_in_pos(&self) -> Input {
((self.0[0].get_bits(0..=1) << 3) |
self.0[1].get_bits(5..=7)).into()
}
/// Set which input is connected to positive input of this channel
#[allow(unused)]
pub fn set_a_in_pos(&mut self, value: Input) {
let value = value as u8;
self.0[0].set_bits(0..=1, value >> 3);
self.0[1].set_bits(5..=7, value & 0x7);
}
reg_bits!(a_in_neg, set_a_in_neg, 1, 0..=4, Input,
"Which input is connected to negative input of this channel");
// const PROPS: &'static [Property<Self>] = &[
// Property::named("enable")
// .readable(&|self_: &Self| self_.enabled().into())
// .writebale(&|self_: &mut Self, value| self_.set_enabled(value != 0)),
// Property::named("setup")
// .readable(&|self_: &Self| self_.0[0].get_bits(4..=5).into())
// .writeable(&|self_: &mut Self, value| {
// self_.0[0].set_bits(4..=5, value as u8);
// }),
// ];
// pub fn props() -> &'static [Property<Self>] {
// Self::PROPS
// }
}
def_reg!(SetupCon, u8, setup_con, 0x20, 2);
impl setup_con::Data {
reg_bit!(bipolar, set_bipolar, 0, 4, "Unipolar (`false`) or bipolar (`true`) coded output");
reg_bit!(refbuf_pos, set_refbuf_pos, 0, 3, "Enable REF+ input buffer");
reg_bit!(refbuf_neg, set_refbuf_neg, 0, 2, "Enable REF- input buffer");
reg_bit!(ainbuf_pos, set_ainbuf_pos, 0, 1, "Enable AIN+ input buffer");
reg_bit!(ainbuf_neg, set_ainbuf_neg, 0, 0, "Enable AIN- input buffer");
reg_bit!(burnout_en, 1, 7, "enables a 10 µA current source on the positive analog input selected and a 10 µA current sink on the negative analog input selected");
reg_bits!(ref_sel, set_ref_sel, 1, 4..=5, RefSource, "Select reference source for conversion");
}
def_reg!(FiltCon, u8, filt_con, 0x28, 2);
impl filt_con::Data {
reg_bit!(sinc3_map, 0, 7, "If set, mapping of filter register changes to directly program the decimation rate of the sinc3 filter");
reg_bit!(enh_filt_en, set_enh_filt_en, 0, 3, "Enable postfilters for enhanced 50Hz and 60Hz rejection");
reg_bits!(enh_filt, set_enh_filt, 0, 0..=2, PostFilter, "Select postfilters for enhanced 50Hz and 60Hz rejection");
reg_bits!(order, set_order, 1, 5..=6, DigitalFilterOrder, "order of the digital filter that processes the modulator data");
reg_bits!(odr, set_odr, 1, 0..=4, "Output data rate");
}
def_reg!(Offset, u8, offset, 0x30, 3);
impl offset::Data {
#[allow(unused)]
pub fn offset(&self) -> u32 {
(u32::from(self.0[0]) << 16) |
(u32::from(self.0[1]) << 8) |
u32::from(self.0[2])
}
#[allow(unused)]
pub fn set_offset(&mut self, value: u32) {
self.0[0] = (value >> 16) as u8;
self.0[1] = (value >> 8) as u8;
self.0[2] = value as u8;
}
}
def_reg!(Gain, u8, gain, 0x38, 3);
impl gain::Data {
#[allow(unused)]
pub fn gain(&self) -> u32 {
(u32::from(self.0[0]) << 16) |
(u32::from(self.0[1]) << 8) |
u32::from(self.0[2])
}
#[allow(unused)]
pub fn set_gain(&mut self, value: u32) {
self.0[0] = (value >> 16) as u8;
self.0[1] = (value >> 8) as u8;
self.0[2] = value as u8;
}
}

View File

@ -0,0 +1,82 @@
use core::slice::{from_raw_parts, from_raw_parts_mut};
use embedded_hal::digital::v2::{InputPin, OutputPin};
pub trait Gpio where Self: Sized {
fn into_output(self) -> GpioOutput<Self>;
fn into_input(self) -> GpioInput<Self>;
}
pub struct GpioInput<PIN>(PIN);
pub struct GpioOutput<PIN>(PIN);
macro_rules! def_gpio {
($PORT: tt, $PIN: tt, $idx: expr) => (
impl $PIN {
fn data(&self) -> &u32 {
let gpio = tm4c129x::$PORT::ptr();
let data = unsafe { from_raw_parts(gpio as *const _ as *mut u32, 0x100) };
&data[(1 << $idx) as usize]
}
fn data_mut(&mut self) -> &mut u32 {
let gpio = tm4c129x::$PORT::ptr();
let data = unsafe { from_raw_parts_mut(gpio as *const _ as *mut u32, 0x100) };
&mut data[(1 << $idx) as usize]
}
}
impl Gpio for $PIN {
fn into_output(self) -> GpioOutput<Self> {
let gpio = unsafe { &*tm4c129x::$PORT::ptr() };
gpio.dir.modify(|r, w| w.dir().bits(r.dir().bits() | (1 << $idx)));
gpio.den.modify(|r, w| w.den().bits(r.den().bits() | (1 << $idx)));
GpioOutput(self)
}
fn into_input(self) -> GpioInput<Self> {
let gpio = unsafe { &*tm4c129x::$PORT::ptr() };
gpio.dir.modify(|r, w| w.dir().bits(r.dir().bits() & !(1 << $idx)));
gpio.den.modify(|r, w| w.den().bits(r.den().bits() | (1 << $idx)));
GpioInput(self)
}
}
impl InputPin for GpioInput<$PIN> {
type Error = ();
fn is_high(&self) -> Result<bool, Self::Error> {
Ok(*self.0.data() != 0)
}
fn is_low(&self) -> Result<bool, Self::Error> {
Ok(*self.0.data() == 0)
}
}
impl OutputPin for GpioOutput<$PIN> {
type Error = ();
fn set_low(&mut self) -> Result<(), Self::Error> {
*self.0.data_mut() = 0;
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
*self.0.data_mut() = 0xFF;
Ok(())
}
}
)
}
pub struct PB4;
def_gpio!(GPIO_PORTB_AHB, PB4, 4);
pub struct PB5;
def_gpio!(GPIO_PORTB_AHB, PB5, 5);
pub struct PE4;
def_gpio!(GPIO_PORTE_AHB, PE4, 4);
pub struct PE5;
def_gpio!(GPIO_PORTE_AHB, PE5, 5);
pub struct PK1;
def_gpio!(GPIO_PORTK, PK1, 1);
pub struct PP2;
def_gpio!(GPIO_PORTP, PP2, 2);
pub struct PP3;
def_gpio!(GPIO_PORTP, PP3, 3);
pub struct PQ4;
def_gpio!(GPIO_PORTQ, PQ4, 4);

146
firmware/src/board/mod.rs Normal file
View File

@ -0,0 +1,146 @@
use cortex_m;
use tm4c129x;
pub mod gpio;
pub mod softspi;
pub mod systick;
pub mod pwm;
const UART_DIV: u32 = (((/*sysclk*/120_000_000 * 8) / /*baud*/115200) + 1) / 2;
pub fn init() {
cortex_m::interrupt::free(|cs| {
let sysctl = unsafe { &*tm4c129x::SYSCTL::ptr() };
// Set up main oscillator
sysctl.moscctl.write(|w| w.noxtal().bit(false));
sysctl.moscctl.modify(|_, w| w.pwrdn().bit(false).oscrng().bit(true));
// Prepare flash for the high-freq clk
sysctl.memtim0.write(|w| unsafe { w.bits(0x01950195u32) });
sysctl.rsclkcfg.write(|w| unsafe { w.bits(0x80000000u32) });
// Set up PLL with fVCO=480 MHz
sysctl.pllfreq1.write(|w| w.q().bits(0).n().bits(4));
sysctl.pllfreq0.write(|w| w.mint().bits(96).pllpwr().bit(true));
sysctl.rsclkcfg.modify(|_, w| w.pllsrc().mosc().newfreq().bit(true));
while !sysctl.pllstat.read().lock().bit() {}
// Switch to PLL (sysclk=120MHz)
sysctl.rsclkcfg.write(|w| unsafe { w.bits(0b1_0_0_1_0011_0000_0000000000_0000000011) });
// Bring up GPIO ports A, B, D, E, F, G, K, L, M, P, Q
sysctl.rcgcgpio.modify(|_, w| {
w.r0().bit(true)
.r1().bit(true)
.r3().bit(true)
.r4().bit(true)
.r5().bit(true)
.r6().bit(true)
.r9().bit(true)
.r10().bit(true)
.r11().bit(true)
.r13().bit(true)
.r14().bit(true)
});
while !sysctl.prgpio.read().r0().bit() {}
while !sysctl.prgpio.read().r1().bit() {}
while !sysctl.prgpio.read().r3().bit() {}
while !sysctl.prgpio.read().r4().bit() {}
while !sysctl.prgpio.read().r5().bit() {}
while !sysctl.prgpio.read().r6().bit() {}
while !sysctl.prgpio.read().r9().bit() {}
while !sysctl.prgpio.read().r10().bit() {}
while !sysctl.prgpio.read().r11().bit() {}
while !sysctl.prgpio.read().r13().bit() {}
while !sysctl.prgpio.read().r14().bit() {}
// Set up UART0
let gpio_a = unsafe { &*tm4c129x::GPIO_PORTA_AHB::ptr() };
gpio_a.dir.write(|w| w.dir().bits(0b11));
gpio_a.den.write(|w| w.den().bits(0b11));
gpio_a.afsel.write(|w| w.afsel().bits(0b11));
gpio_a.pctl.write(|w| unsafe { w.pmc0().bits(1).pmc1().bits(1) });
sysctl.rcgcuart.modify(|_, w| w.r0().bit(true));
while !sysctl.pruart.read().r0().bit() {}
let uart_0 = unsafe { &*tm4c129x::UART0::ptr() };
uart_0.cc.write(|w| w.cs().sysclk());
uart_0.ibrd.write(|w| w.divint().bits((UART_DIV / 64) as u16));
uart_0.fbrd.write(|w| w.divfrac().bits((UART_DIV % 64) as u8));
uart_0.lcrh.write(|w| w.wlen()._8().fen().bit(true));
uart_0.ctl.write(|w| w.rxe().bit(true).txe().bit(true).uarten().bit(true));
// Set up PWMs
let gpio_m = unsafe { &*tm4c129x::GPIO_PORTM::ptr() };
// Output
gpio_m.dir.write(|w| w.dir().bits(0xff));
// Enable
gpio_m.den.write(|w| w.den().bits(0xff));
// Alternate function
gpio_m.afsel.write(|w| w.afsel().bits(0xff));
// Function: Timer PWM
gpio_m.pctl.write(|w| unsafe {
w
// t2ccp0
.pmc0().bits(3)
// t2ccp1
.pmc1().bits(3)
// t3ccp0
.pmc2().bits(3)
// t3ccp1
.pmc3().bits(3)
// t4ccp0
.pmc4().bits(3)
// t4ccp1
.pmc5().bits(3)
// t5ccp0
.pmc6().bits(3)
// t5ccp1
.pmc7().bits(3)
});
// Enable timers
sysctl.rcgctimer.write(|w| w
.r2().set_bit()
.r3().set_bit()
.r4().set_bit()
.r5().set_bit()
);
// Reset timers
sysctl.srtimer.write(|w| w
.r2().set_bit()
.r3().set_bit()
.r4().set_bit()
.r5().set_bit()
);
sysctl.srtimer.write(|w| w
.r2().clear_bit()
.r3().clear_bit()
.r4().clear_bit()
.r5().clear_bit()
);
fn timers_ready(sysctl: &tm4c129x::sysctl::RegisterBlock) -> bool {
let prtimer = sysctl.prtimer.read();
prtimer.r2().bit() &&
prtimer.r3().bit() &&
prtimer.r4().bit() &&
prtimer.r5().bit()
}
while !timers_ready(sysctl) {}
systick::init(cs);
});
}
pub fn get_mac_address() -> [u8; 6] {
let (userreg0, userreg1) = cortex_m::interrupt::free(|_cs| {
let flashctl = unsafe { &*tm4c129x::FLASH_CTRL::ptr() };
(flashctl.userreg0.read().bits(),
flashctl.userreg1.read().bits())
});
[userreg0 as u8, (userreg0 >> 8) as u8, (userreg0 >> 16) as u8,
userreg1 as u8, (userreg1 >> 8) as u8, (userreg1 >> 16) as u8]
}

138
firmware/src/board/pwm.rs Normal file
View File

@ -0,0 +1,138 @@
use tm4c129x::{
TIMER2, TIMER3, TIMER4, TIMER5,
};
pub struct T2CCP0;
pub struct T2CCP1;
pub struct T3CCP0;
pub struct T3CCP1;
pub struct T4CCP0;
pub struct T4CCP1;
pub struct T5CCP0;
pub struct T5CCP1;
pub trait PwmPeripheral {
type ChannelA: PwmChannel;
type ChannelB: PwmChannel;
fn split() -> (Self::ChannelA, Self::ChannelB);
}
macro_rules! pwm_peripheral {
($TIMER: ty, $A: tt, $B: tt) => {
impl PwmPeripheral for $TIMER {
type ChannelA = $A;
type ChannelB = $B;
fn split() -> (Self::ChannelA, Self::ChannelB) {
let regs = unsafe { &*Self::ptr() };
regs.cfg.write(|w| unsafe { w.bits(4) });
let mut a = $A;
a.configure();
let mut b = $B;
b.configure();
(a, b)
}
}
};
}
pwm_peripheral!(TIMER2, T2CCP0, T2CCP1);
pwm_peripheral!(TIMER3, T3CCP0, T3CCP1);
pwm_peripheral!(TIMER4, T4CCP0, T4CCP1);
pwm_peripheral!(TIMER5, T5CCP0, T5CCP1);
pub trait PwmChannel {
fn configure(&mut self);
fn get(&mut self) -> (u16, u16);
fn set(&mut self, width: u16, total: u16);
}
macro_rules! pwm_channel_a {
($CHANNEL: ty, $TIMER: tt) => {
impl PwmChannel for $CHANNEL {
fn configure(&mut self) {
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
timer.tamr.modify(|_, w| unsafe {
w
.taams().bit(true)
.tacmr().bit(false)
.tamr().bits(2)
});
timer.ctl.modify(|_, w| {
w
.tapwml().bit(false)
});
// no prescaler
// no interrupts
timer.tailr.write(|w| unsafe { w.bits(0xFFFF) });
timer.tamatchr.write(|w| unsafe { w.bits(0x0) });
timer.ctl.modify(|_, w| {
w
.taen().bit(true)
});
}
fn get(&mut self) -> (u16, u16) {
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
(timer.tamatchr.read().bits() as u16,
timer.tailr.read().bits() as u16)
}
fn set(&mut self, width: u16, total: u16) {
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
timer.tamatchr.write(|w| unsafe { w.bits(width.into()) });
timer.tailr.write(|w| unsafe { w.bits(total.into()) });
}
}
};
}
macro_rules! pwm_channel_b {
($CHANNEL: ty, $TIMER: tt) => {
impl PwmChannel for $CHANNEL {
fn configure(&mut self) {
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
timer.tbmr.modify(|_, w| unsafe {
w
.tbams().bit(true)
.tbcmr().bit(false)
.tbmr().bits(2)
});
timer.ctl.modify(|_, w| {
w
.tbpwml().bit(false)
});
// no prescaler
// no interrupts
timer.tbilr.write(|w| unsafe { w.bits(0xFFFF) });
timer.tbmatchr.write(|w| unsafe { w.bits(0x0) });
timer.ctl.modify(|_, w| {
w
.tben().bit(true)
});
}
fn get(&mut self) -> (u16, u16) {
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
(timer.tbmatchr.read().bits() as u16,
timer.tbilr.read().bits() as u16)
}
fn set(&mut self, width: u16, total: u16) {
let timer = unsafe { &*tm4c129x::$TIMER::ptr() };
timer.tbmatchr.write(|w| unsafe { w.bits(width.into()) });
timer.tbilr.write(|w| unsafe { w.bits(total.into()) });
}
}
};
}
pwm_channel_a!(T2CCP0, TIMER2);
pwm_channel_b!(T2CCP1, TIMER2);
pwm_channel_a!(T3CCP0, TIMER3);
pwm_channel_b!(T3CCP1, TIMER3);
pwm_channel_a!(T4CCP0, TIMER4);
pwm_channel_b!(T4CCP1, TIMER4);
pwm_channel_a!(T5CCP0, TIMER5);
pwm_channel_b!(T5CCP1, TIMER5);

View File

@ -0,0 +1,148 @@
use embedded_hal::spi::FullDuplex;
use embedded_hal::digital::v2::{InputPin, OutputPin};
use embedded_hal::blocking::spi::Transfer;
use nb::Error::WouldBlock;
/// Bit-banged Mode3 SPI
pub struct SoftSpi<SCK, MOSI, MISO> {
sck: SCK,
mosi: MOSI,
miso: MISO,
state: State,
input: Option<u8>,
}
#[derive(PartialEq)]
enum State {
Idle,
Transfer {
clock_phase: bool,
mask: u8,
output: u8,
input: u8,
},
}
impl<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin> SoftSpi<SCK, MOSI, MISO> {
pub fn new(mut sck: SCK, mut mosi: MOSI, miso: MISO) -> Self {
let _ = sck.set_high();
let _ = mosi.set_low();
SoftSpi {
sck, mosi, miso,
state: State::Idle,
input: None,
}
}
/// Call this at twice the data rate
pub fn tick(&mut self) {
match self.state {
State::Idle => {}
State::Transfer { clock_phase: false,
mask, output, input } => {
if output & mask != 0 {
let _ = self.mosi.set_high();
} else {
let _ = self.mosi.set_low();
}
let _ = self.sck.set_low();
self.state = State::Transfer {
clock_phase: true,
mask, output, input,
};
}
State::Transfer { clock_phase: true,
mask, output, mut input } => {
if self.miso.is_high().unwrap_or(false) {
input |= mask;
}
let _ = self.sck.set_high();
if mask != 1 {
self.state = State::Transfer {
clock_phase: false,
mask: mask >> 1,
output, input,
};
} else {
self.input = Some(input);
self.state = State::Idle;
}
}
}
}
pub fn run<F: FnMut()>(&mut self, delay: &'_ mut F) {
while self.state != State::Idle {
self.tick();
delay();
}
}
}
impl<SCK: OutputPin, MOSI: OutputPin, MISO: InputPin> FullDuplex<u8> for SoftSpi<SCK, MOSI, MISO> {
type Error = ();
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
match self.input.take() {
Some(input) =>
Ok(input),
None if self.state == State::Idle =>
Err(nb::Error::Other(())),
None =>
Err(WouldBlock),
}
}
fn send(&mut self, output: u8) -> Result<(), nb::Error<Self::Error>> {
match self.state {
State::Idle => {
self.state = State::Transfer {
clock_phase: false,
mask: 0x80,
output,
input: 0,
};
Ok(())
}
_ => Err(WouldBlock)
}
}
}
pub struct SyncSoftSpi<'d, SCK: OutputPin, MOSI: OutputPin, MISO: InputPin, D: FnMut()> {
spi: SoftSpi<SCK, MOSI, MISO>,
delay: &'d mut D,
}
impl<'d, SCK: OutputPin, MOSI: OutputPin, MISO: InputPin, D: FnMut()> SyncSoftSpi<'d, SCK, MOSI, MISO, D> {
pub fn new(spi: SoftSpi<SCK, MOSI, MISO>, delay: &'d mut D) -> Self {
SyncSoftSpi { spi, delay }
}
fn retry<R, E, F>(&mut self, f: &F) -> Result<R, E>
where
F: Fn(&'_ mut SoftSpi<SCK, MOSI, MISO>) -> Result<R, nb::Error<E>>
{
loop {
match f(&mut self.spi) {
Ok(r) => return Ok(r),
Err(nb::Error::Other(e)) => return Err(e),
Err(WouldBlock) => self.spi.run(self.delay),
}
}
}
}
impl<'d, SCK: OutputPin, MOSI: OutputPin, MISO: InputPin, D: FnMut()> Transfer<u8> for SyncSoftSpi<'d, SCK, MOSI, MISO, D> {
// TODO: proper type
type Error = ();
fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
for b in words.iter_mut() {
self.retry(&|spi| spi.send(*b))?;
*b = self.retry(&|spi| spi.read())?;
}
Ok(words)
}
}

View File

@ -0,0 +1,43 @@
use core::cell::RefCell;
use cortex_m::interrupt::Mutex;
use cortex_m::peripheral::{SYST, syst::SystClkSource};
use cortex_m_rt::exception;
use bare_metal::CriticalSection;
static mut TIME: Mutex<RefCell<u64>> = Mutex::new(RefCell::new(0));
/// In HZ
const RATE: u32 = 10;
/// Period between two interrupts in ns
const INTERVAL: u64 = 1_000_000 / RATE as u64;
fn syst() -> &'static mut SYST {
#[allow(mutable_transmutes)]
unsafe { core::mem::transmute(&*SYST::ptr()) }
}
pub fn init(_cs: &CriticalSection) {
let syst = syst();
// syst.set_clock_source(SystClkSource::Core);
syst.set_clock_source(SystClkSource::External);
syst.set_reload(100 * SYST::get_ticks_per_10ms() / RATE);
syst.clear_current();
syst.enable_interrupt();
syst.enable_counter();
}
#[exception]
unsafe fn SysTick() {
cortex_m::interrupt::free(|cs| {
TIME.borrow(cs).replace_with(|time| *time + INTERVAL);
});
}
pub fn get_time() -> u64 {
let base = cortex_m::interrupt::free(|cs| {
*unsafe { &mut TIME }.borrow(cs).borrow()
});
let syst_current = u64::from(SYST::get_current());
let syst_reload = u64::from(SYST::get_reload());
let precise = INTERVAL - (INTERVAL * syst_current / syst_reload);
base + u64::from(precise)
}

View File

@ -0,0 +1,533 @@
use core::fmt;
use nom::{
IResult,
branch::alt,
bytes::complete::{is_a, tag, take_while1},
character::{is_digit, complete::{char, one_of}},
combinator::{complete, map, opt, value},
sequence::{preceded, separated_pair},
multi::{fold_many0, fold_many1},
error::ErrorKind,
};
use lexical_core as lexical;
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Parser(ErrorKind),
Incomplete,
UnexpectedInput(u8),
ParseNumber(lexical::Error)
}
impl<'t> From<nom::Err<(&'t [u8], ErrorKind)>> for Error {
fn from(e: nom::Err<(&'t [u8], ErrorKind)>) -> Self {
match e {
nom::Err::Incomplete(_) =>
Error::Incomplete,
nom::Err::Error((_, e)) =>
Error::Parser(e),
nom::Err::Failure((_, e)) =>
Error::Parser(e),
}
}
}
impl From<lexical::Error> for Error {
fn from(e: lexical::Error) -> Self {
Error::ParseNumber(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
Error::Incomplete =>
"incomplete input".fmt(fmt),
Error::UnexpectedInput(c) => {
"unexpected input: ".fmt(fmt)?;
c.fmt(fmt)
}
Error::Parser(e) => {
"parser: ".fmt(fmt)?;
(e as &dyn core::fmt::Debug).fmt(fmt)
}
Error::ParseNumber(e) => {
"parsing number: ".fmt(fmt)?;
(e as &dyn core::fmt::Debug).fmt(fmt)
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ShowCommand {
Input,
Reporting,
Pwm,
Pid,
SteinhartHart,
PostFilter,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PidParameter {
Target,
KP,
KI,
KD,
OutputMin,
OutputMax,
IntegralMin,
IntegralMax,
}
/// Steinhart-Hart equation parameter
#[derive(Debug, Clone, PartialEq)]
pub enum ShParameter {
A,
B,
C,
ParallelR,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PwmConfig {
pub width: u16,
pub total: u16,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PwmMode {
Manual(PwmConfig),
Pid,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PwmSetup {
ISet(PwmMode),
MaxIPos(PwmConfig),
MaxINeg(PwmConfig),
MaxV(PwmConfig),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
Quit,
Show(ShowCommand),
Reporting(bool),
Pwm {
channel: usize,
setup: PwmSetup,
},
Pid {
channel: usize,
parameter: PidParameter,
value: f32,
},
SteinhartHart {
channel: usize,
parameter: ShParameter,
value: f32,
},
PostFilter {
channel: usize,
rate: f32,
},
}
fn end(input: &[u8]) -> IResult<&[u8], ()> {
complete(
fold_many0(
one_of("\r\n\t "),
(), |(), _| ()
)
)(input)
}
fn whitespace(input: &[u8]) -> IResult<&[u8], ()> {
fold_many1(char(' '), (), |(), _| ())(input)
}
fn unsigned(input: &[u8]) -> IResult<&[u8], Result<u16, Error>> {
take_while1(is_digit)(input)
.map(|(input, digits)| {
let result = lexical::parse(digits)
.map_err(|e| e.into());
(input, result)
})
}
fn float(input: &[u8]) -> IResult<&[u8], Result<f32, Error>> {
let (input, sign) = opt(is_a("-"))(input)?;
let negative = sign.is_some();
let (input, digits) = take_while1(|c| is_digit(c) || c == '.' as u8)(input)?;
let result = lexical::parse(digits)
.map(|result: f32| if negative { -result } else { result })
.map_err(|e| e.into());
Ok((input, result))
}
fn off_on(input: &[u8]) -> IResult<&[u8], bool> {
alt((value(false, tag("off")),
value(true, tag("on"))
))(input)
}
fn channel(input: &[u8]) -> IResult<&[u8], usize> {
map(one_of("01"), |c| (c as usize) - ('0' as usize))(input)
}
fn report(input: &[u8]) -> IResult<&[u8], Command> {
preceded(
tag("report"),
alt((
preceded(
whitespace,
preceded(
tag("mode"),
alt((
preceded(
whitespace,
// `report mode <on | off>` - Switch repoting mode
map(off_on, Command::Reporting)
),
// `report mode` - Show current reporting state
value(Command::Show(ShowCommand::Reporting), end)
))
)),
// `report` - Report once
value(Command::Show(ShowCommand::Input), end)
))
)(input)
}
/// `pwm ... <width> <total>` - Set pwm duty cycle
fn pwm_config(input: &[u8]) -> IResult<&[u8], Result<PwmConfig, Error>> {
let (input, width) = unsigned(input)?;
let width = match width {
Ok(width) => width,
Err(e) => return Ok((input, Err(e.into()))),
};
let (input, _) = whitespace(input)?;
let (input, total) = unsigned(input)?;
let total = match total {
Ok(total) => total,
Err(e) => return Ok((input, Err(e.into()))),
};
Ok((input, Ok(PwmConfig { width, total })))
}
fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<PwmSetup, Error>> {
alt((
map(
preceded(
tag("max_i_pos"),
preceded(
whitespace,
pwm_config
)
),
|result| result.map(PwmSetup::MaxIPos)
),
map(
preceded(
tag("max_i_neg"),
preceded(
whitespace,
pwm_config
)
),
|result| result.map(PwmSetup::MaxINeg)
),
map(
preceded(
tag("max_v"),
preceded(
whitespace,
pwm_config
)
),
|result| result.map(PwmSetup::MaxV)
),
map(pwm_config, |result| result.map(|config| {
PwmSetup::ISet(PwmMode::Manual(config))
}))
))(input)
}
/// `pwm <0-1> pid` - Set PWM to be controlled by PID
fn pwm_pid(input: &[u8]) -> IResult<&[u8], Result<PwmSetup, Error>> {
value(Ok(PwmSetup::ISet(PwmMode::Pid)), tag("pid"))(input)
}
fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("pwm")(input)?;
alt((
preceded(
whitespace,
map(
separated_pair(
channel,
whitespace,
alt((
pwm_pid,
pwm_setup
))
),
|(channel, setup)| setup.map(|setup| Command::Pwm { channel, setup })
)
),
value(Ok(Command::Show(ShowCommand::Pwm)), end)
))(input)
}
/// `pid <0-1> <parameter> <value>`
fn pid_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, channel) = channel(input)?;
let (input, _) = whitespace(input)?;
let (input, parameter) =
alt((value(PidParameter::Target, tag("target")),
value(PidParameter::KP, tag("kp")),
value(PidParameter::KI, tag("ki")),
value(PidParameter::KD, tag("kd")),
value(PidParameter::OutputMin, tag("output_min")),
value(PidParameter::OutputMax, tag("output_max")),
value(PidParameter::IntegralMin, tag("integral_min")),
value(PidParameter::IntegralMax, tag("integral_max"))
))(input)?;
let (input, _) = whitespace(input)?;
let (input, value) = float(input)?;
let result = value
.map(|value| Command::Pid { channel, parameter, value });
Ok((input, result))
}
/// `pid` | `pid <pid_parameter>`
fn pid(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("pid")(input)?;
alt((
preceded(
whitespace,
pid_parameter
),
value(Ok(Command::Show(ShowCommand::Pid)), end)
))(input)
}
/// `s-h <0-1> <parameter> <value>`
fn steinhart_hart_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, channel) = channel(input)?;
let (input, _) = whitespace(input)?;
let (input, parameter) =
alt((value(ShParameter::A, tag("a")),
value(ShParameter::B, tag("b")),
value(ShParameter::C, tag("c")),
value(ShParameter::ParallelR, tag("parallel_r"))
))(input)?;
let (input, _) = whitespace(input)?;
let (input, value) = float(input)?;
let result = value
.map(|value| Command::SteinhartHart { channel, parameter, value });
Ok((input, result))
}
/// `s-h` | `s-h <steinhart_hart_parameter>`
fn steinhart_hart(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("s-h")(input)?;
alt((
preceded(
whitespace,
steinhart_hart_parameter
),
value(Ok(Command::Show(ShowCommand::SteinhartHart)), end)
))(input)
}
fn postfilter(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("postfilter")(input)?;
alt((
preceded(
whitespace,
|input| {
let (input, channel) = channel(input)?;
let (input, _) = whitespace(input)?;
let (input, _) = tag("rate")(input)?;
let (input, _) = whitespace(input)?;
let (input, rate) = float(input)?;
let result = rate
.map(|rate| Command::PostFilter {
channel, rate,
});
Ok((input, result))
}
),
value(Ok(Command::Show(ShowCommand::PostFilter)), end)
))(input)
}
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
alt((value(Ok(Command::Quit), tag("quit")),
map(report, Ok),
pwm,
pid,
steinhart_hart,
postfilter,
))(input)
}
impl Command {
pub fn parse(input: &[u8]) -> Result<Self, Error> {
match command(input) {
Ok((b"", result)) =>
result,
Ok((input_remain, _)) =>
Err(Error::UnexpectedInput(input_remain[0])),
Err(e) =>
Err(e.into()),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_quit() {
let command = Command::parse(b"quit");
assert_eq!(command, Ok(Command::Quit));
}
#[test]
fn parse_report() {
let command = Command::parse(b"report");
assert_eq!(command, Ok(Command::Show(ShowCommand::Input)));
}
#[test]
fn parse_report_mode() {
let command = Command::parse(b"report mode");
assert_eq!(command, Ok(Command::Show(ShowCommand::Reporting)));
}
#[test]
fn parse_report_mode_on() {
let command = Command::parse(b"report mode on");
assert_eq!(command, Ok(Command::Reporting(true)));
}
#[test]
fn parse_report_mode_off() {
let command = Command::parse(b"report mode off");
assert_eq!(command, Ok(Command::Reporting(false)));
}
#[test]
fn parse_pwm_manual() {
let command = Command::parse(b"pwm 1 16383 65535");
assert_eq!(command, Ok(Command::Pwm {
channel: 1,
setup: PwmSetup::ISet(PwmMode::Manual(PwmConfig {
width: 16383,
total: 65535,
})),
}));
}
#[test]
fn parse_pwm_pid() {
let command = Command::parse(b"pwm 0 pid");
assert_eq!(command, Ok(Command::Pwm {
channel: 0,
setup: PwmSetup::ISet(PwmMode::Pid),
}));
}
#[test]
fn parse_pwm_max_i_pos() {
let command = Command::parse(b"pwm 0 max_i_pos 7 13");
assert_eq!(command, Ok(Command::Pwm {
channel: 0,
setup: PwmSetup::MaxIPos(PwmConfig {
width: 7,
total: 13,
}),
}));
}
#[test]
fn parse_pwm_max_i_neg() {
let command = Command::parse(b"pwm 0 max_i_neg 128 65535");
assert_eq!(command, Ok(Command::Pwm {
channel: 0,
setup: PwmSetup::MaxINeg(PwmConfig {
width: 128,
total: 65535,
}),
}));
}
#[test]
fn parse_pwm_max_v() {
let command = Command::parse(b"pwm 0 max_v 32768 65535");
assert_eq!(command, Ok(Command::Pwm {
channel: 0,
setup: PwmSetup::MaxV(PwmConfig {
width: 32768,
total: 65535,
}),
}));
}
#[test]
fn parse_pid() {
let command = Command::parse(b"pid");
assert_eq!(command, Ok(Command::Show(ShowCommand::Pid)));
}
#[test]
fn parse_pid_target() {
let command = Command::parse(b"pid 0 target 36.5");
assert_eq!(command, Ok(Command::Pid {
channel: 0,
parameter: PidParameter::Target,
value: 36.5,
}));
}
#[test]
fn parse_pid_integral_max() {
let command = Command::parse(b"pid 1 integral_max 2000");
assert_eq!(command, Ok(Command::Pid {
channel: 1,
parameter: PidParameter::IntegralMax,
value: 2000.0,
}));
}
#[test]
fn parse_steinhart_hart() {
let command = Command::parse(b"s-h");
assert_eq!(command, Ok(Command::Show(ShowCommand::SteinhartHart)));
}
#[test]
fn parse_steinhart_hart_parallel_r() {
let command = Command::parse(b"s-h 1 parallel_r 23.05");
assert_eq!(command, Ok(Command::SteinhartHart {
channel: 1,
parameter: ShParameter::ParallelR,
value: 23.05,
}));
}
#[test]
fn parse_postfilter_rate() {
let command = Command::parse(b"postfilter 0 rate 21");
assert_eq!(command, Ok(Command::PostFilter {
channel: 0,
rate: 21.0,
}));
}
}

447
firmware/src/ethmac.rs Normal file
View File

@ -0,0 +1,447 @@
use core::{slice, cmp};
use cortex_m::{self, asm::delay};
use tm4c129x;
use smoltcp::Result;
use smoltcp::time::Instant;
use smoltcp::wire::EthernetAddress;
use smoltcp::phy;
const EPHY_BMCR: u8 = 0x00; // Ethernet PHY Basic Mode Control
#[allow(dead_code)]
const EPHY_BMSR: u8 = 0x01; // Ethernet PHY Basic Mode Status
const EPHY_ID1: u8 = 0x02; // Ethernet PHY Identifier Register 1
const EPHY_ID2: u8 = 0x03; // Ethernet PHY Identifier Register 2
const EPHY_REGCTL: u8 = 0x0D; // Ethernet PHY Register Control
const EPHY_ADDAR: u8 = 0x0E; // Ethernet PHY Address or Data
const EPHY_LEDCFG: u8 = 0x25; // Ethernet PHY LED Configuration
// Transmit DMA descriptor flags
const EMAC_TDES0_OWN: u32 = 0x80000000; // Indicates that the descriptor is owned by the DMA
const EMAC_TDES0_LS: u32 = 0x20000000; // Last Segment
const EMAC_TDES0_FS: u32 = 0x10000000; // First Segment
const EMAC_TDES0_TCH: u32 = 0x00100000; // Second Address Chained
#[allow(dead_code)]
const EMAC_TDES1_TBS1: u32 = 0x00001FFF; // Transmit Buffer 1 Size
// Receive DMA descriptor flags
const EMAC_RDES0_OWN: u32 = 0x80000000; // indicates that the descriptor is owned by the DMA
const EMAC_RDES0_FL: u32 = 0x3FFF0000; // Frame Length
const EMAC_RDES0_ES: u32 = 0x00008000; // Error Summary
const EMAC_RDES0_FS: u32 = 0x00000200; // First Descriptor
const EMAC_RDES0_LS: u32 = 0x00000100; // Last Descriptor
const EMAC_RDES1_RCH: u32 = 0x00004000; // Second Address Chained
const EMAC_RDES1_RBS1: u32 = 0x00001FFF; // Receive Buffer 1 Size
const ETH_DESC_U32_SIZE: usize = 8;
const ETH_TX_BUFFER_COUNT: usize = 2;
const ETH_TX_BUFFER_SIZE: usize = 1536;
const ETH_RX_BUFFER_COUNT: usize = 3;
const ETH_RX_BUFFER_SIZE: usize = 1536;
fn phy_read(reg_addr: u8) -> u16 {
cortex_m::interrupt::free(|_cs| {
let emac0 = unsafe { &*tm4c129x::EMAC0::ptr() };
// Make sure the MII is idle
while emac0.miiaddr.read().miib().bit() {};
// Tell the MAC to read the given PHY register
unsafe {
emac0.miiaddr.write(|w| {
w.cr()._100_150()
.mii().bits(reg_addr & 0x1F)
.miib().bit(true)
});
}
// Wait for the read to complete
while emac0.miiaddr.read().miib().bit() {};
emac0.miidata.read().data().bits()
})
}
fn phy_write(reg_addr: u8, reg_data: u16) {
cortex_m::interrupt::free(|_cs| {
let emac0 = unsafe { &*tm4c129x::EMAC0::ptr() };
// Make sure the MII is idle
while emac0.miiaddr.read().miib().bit() {};
unsafe {
emac0.miidata.write(|w| {
w.data().bits(reg_data)
});
// Tell the MAC to write the given PHY register
emac0.miiaddr.write(|w| {
w.cr()._100_150()
.mii().bits(reg_addr & 0x1F)
.miiw().bit(true)
.miib().bit(true)
});
}
// Wait for the read to complete
while emac0.miiaddr.read().miib().bit() {};
})
}
// Writes a value to an extended PHY register in MMD address space
fn phy_write_ext(reg_addr: u8, reg_data: u16) {
phy_write(EPHY_REGCTL, 0x001F); // set address (datasheet page 1612)
phy_write(EPHY_ADDAR, reg_addr as u16);
phy_write(EPHY_REGCTL, 0x401F); // set write mode
phy_write(EPHY_ADDAR, reg_data);
}
struct RxRing {
desc_buf: [u32; ETH_RX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
cur_desc: usize,
counter: u32,
pkt_buf: [u8; ETH_RX_BUFFER_COUNT * ETH_RX_BUFFER_SIZE],
}
impl RxRing {
fn new() -> RxRing {
RxRing {
desc_buf: [0; ETH_RX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
cur_desc: 0,
counter: 0,
pkt_buf: [0; ETH_RX_BUFFER_COUNT * ETH_RX_BUFFER_SIZE],
}
}
fn init(&mut self) {
// Initialize RX DMA descriptors
for x in 0..ETH_RX_BUFFER_COUNT {
let p = x * ETH_DESC_U32_SIZE;
let r = x * ETH_RX_BUFFER_SIZE;
// The descriptor is initially owned by the DMA
self.desc_buf[p + 0] = EMAC_RDES0_OWN;
// Use chain structure rather than ring structure
self.desc_buf[p + 1] =
EMAC_RDES1_RCH | ((ETH_RX_BUFFER_SIZE as u32) & EMAC_RDES1_RBS1);
// Receive buffer address
self.desc_buf[p + 2] = (&self.pkt_buf[r] as *const u8) as u32;
// Next descriptor address
if x != ETH_RX_BUFFER_COUNT - 1 {
self.desc_buf[p + 3] =
(&self.desc_buf[p + ETH_DESC_U32_SIZE] as *const u32) as u32;
} else {
self.desc_buf[p + 3] =
(&self.desc_buf[0] as *const u32) as u32;
}
// Extended status
self.desc_buf[p + 4] = 0;
// Reserved field
self.desc_buf[p + 5] = 0;
// Transmit frame time stamp
self.desc_buf[p + 6] = 0;
self.desc_buf[p + 7] = 0;
}
}
fn buf_owned(&self) -> bool {
self.desc_buf[self.cur_desc + 0] & EMAC_RDES0_OWN == 0
}
fn buf_valid(&self) -> bool {
self.desc_buf[self.cur_desc + 0] &
(EMAC_RDES0_FS | EMAC_RDES0_LS | EMAC_RDES0_ES) ==
(EMAC_RDES0_FS | EMAC_RDES0_LS)
}
unsafe fn buf_as_slice<'a>(&self) -> &'a mut [u8] {
let len = (self.desc_buf[self.cur_desc + 0] & EMAC_RDES0_FL) >> 16;
let len = cmp::min(len as usize, ETH_RX_BUFFER_SIZE);
let addr = self.desc_buf[self.cur_desc + 2] as *mut u8;
slice::from_raw_parts_mut(addr, len)
}
fn buf_release(&mut self) {
self.cur_desc += ETH_DESC_U32_SIZE;
if self.cur_desc == self.desc_buf.len() {
self.cur_desc = 0;
}
self.counter += 1;
self.desc_buf[self.cur_desc + 0] = EMAC_RDES0_OWN;
}
}
struct TxRing {
desc_buf: [u32; ETH_TX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
cur_desc: usize,
counter: u32,
pkt_buf: [u8; ETH_TX_BUFFER_COUNT * ETH_TX_BUFFER_SIZE],
}
impl TxRing {
fn new() -> TxRing {
TxRing {
desc_buf: [0; ETH_TX_BUFFER_COUNT * ETH_DESC_U32_SIZE],
cur_desc: 0,
counter: 0,
pkt_buf: [0; ETH_TX_BUFFER_COUNT * ETH_TX_BUFFER_SIZE],
}
}
fn init(&mut self) {
// Initialize TX DMA descriptors
for x in 0..ETH_TX_BUFFER_COUNT {
let p = x * ETH_DESC_U32_SIZE;
let r = x * ETH_TX_BUFFER_SIZE;
// Initialize transmit flags
self.desc_buf[p + 0] = 0;
// Initialize transmit buffer size
self.desc_buf[p + 1] = 0;
// Transmit buffer address
self.desc_buf[p + 2] = (&self.pkt_buf[r] as *const u8) as u32;
// Next descriptor address
if x != ETH_TX_BUFFER_COUNT - 1 {
self.desc_buf[p + 3] =
(&self.desc_buf[p + ETH_DESC_U32_SIZE] as *const u32) as u32;
} else {
self.desc_buf[p + 3] =
(&self.desc_buf[0] as *const u32) as u32;
}
// Reserved fields
self.desc_buf[p + 4] = 0;
self.desc_buf[p + 5] = 0;
// Transmit frame time stamp
self.desc_buf[p + 6] = 0;
self.desc_buf[p + 7] = 0;
}
}
fn buf_owned(&self) -> bool {
self.desc_buf[self.cur_desc + 0] & EMAC_TDES0_OWN == 0
}
unsafe fn buf_as_slice<'a>(&mut self, len: usize) -> &'a mut [u8] {
let len = cmp::min(len, ETH_TX_BUFFER_SIZE);
self.desc_buf[self.cur_desc + 1] = len as u32;
let addr = self.desc_buf[self.cur_desc + 2] as *mut u8;
slice::from_raw_parts_mut(addr, len)
}
fn buf_release(&mut self) {
self.desc_buf[self.cur_desc + 0] =
EMAC_TDES0_OWN | EMAC_TDES0_LS | EMAC_TDES0_FS | EMAC_TDES0_TCH;
cortex_m::interrupt::free(|_cs| {
let emac0 = unsafe { &*tm4c129x::EMAC0::ptr() };
// Clear TU flag to resume processing
emac0.dmaris.write(|w| w.tu().bit(true));
// Instruct the DMA to poll the transmit descriptor list
unsafe { emac0.txpolld.write(|w| w.tpd().bits(0)); }
});
self.cur_desc += ETH_DESC_U32_SIZE;
if self.cur_desc == self.desc_buf.len() {
self.cur_desc = 0;
}
self.counter += 1;
}
}
pub struct Device {
rx: RxRing,
tx: TxRing,
}
impl Device {
pub fn new() -> Device {
Device {
rx: RxRing::new(),
tx: TxRing::new(),
}
}
// After `init` is called, `Device` shall not be moved.
pub unsafe fn init(&mut self, mac: EthernetAddress) {
self.rx.init();
self.tx.init();
cortex_m::interrupt::free(|_cs| {
let sysctl = &*tm4c129x::SYSCTL::ptr();
let emac0 = &*tm4c129x::EMAC0::ptr();
sysctl.rcgcemac.modify(|_, w| w.r0().bit(true)); // Bring up MAC
sysctl.sremac.modify(|_, w| w.r0().bit(true)); // Activate MAC reset
delay(16);
sysctl.sremac.modify(|_, w| w.r0().bit(false)); // Dectivate MAC reset
sysctl.rcgcephy.modify(|_, w| w.r0().bit(true)); // Bring up PHY
sysctl.srephy.modify(|_, w| w.r0().bit(true)); // Activate PHY reset
delay(16);
sysctl.srephy.modify(|_, w| w.r0().bit(false)); // Dectivate PHY reset
while !sysctl.premac.read().r0().bit() {} // Wait for the MAC to come out of reset
while !sysctl.prephy.read().r0().bit() {} // Wait for the PHY to come out of reset
delay(10000);
emac0.dmabusmod.modify(|_, w| w.swr().bit(true)); // Reset MAC DMA
while emac0.dmabusmod.read().swr().bit() {} // Wait for the MAC DMA to come out of reset
delay(1000);
emac0.miiaddr.write(|w| w.cr()._100_150()); // Set the MII CSR clock speed.
// Checking PHY
if (phy_read(EPHY_ID1) != 0x2000) | (phy_read(EPHY_ID2) != 0xA221) {
panic!("PHY ID error!");
}
// Reset PHY transceiver
phy_write(EPHY_BMCR, 1); // Initiate MII reset
while (phy_read(EPHY_BMCR) & 1) == 1 {}; // Wait for the reset to be completed
// Configure PHY LEDs
phy_write_ext(EPHY_LEDCFG, 0x0008); // LED0 Link OK/Blink on TX/RX Activity
// Tell the PHY to start an auto-negotiation cycle
phy_write(EPHY_BMCR, 0b00010010_00000000); // ANEN and RESTARTAN
// Set the DMA operation mode
emac0.dmaopmode.write(|w|
w.rsf().bit(true) // Receive Store and Forward
.tsf().bit(true) // Transmit Store and Forward
.ttc()._64() // Transmit Threshold Control
.rtc()._64() // Receive Threshold Control
);
// Set the bus mode register.
emac0.dmabusmod.write(|w|
w.atds().bit(true)
.aal().bit(true) // Address Aligned Beats
.usp().bit(true) // Use Separate Programmable Burst Length ???
.rpbl().bits(1) // RX DMA Programmable Burst Length
.pbl().bits(1) // Programmable Burst Length
.pr().bits(0) // Priority Ratio 1:1
);
// Disable all the MMC interrupts as these are enabled by default at reset.
emac0.mmcrxim.write(|w| w.bits(0xFFFFFFFF));
emac0.mmctxim.write(|w| w.bits(0xFFFFFFFF));
// Set MAC configuration options
emac0.cfg.write(|w|
w.dupm().bit(true) // MAC operates in full-duplex mode
.ipc().bit(true) // Checksum Offload Enable
.prelen()._7() // 7 bytes of preamble
.ifg()._96() // 96 bit times
.bl()._1024() // Back-Off Limit 1024
.ps().bit(true) // ?
);
// Set the maximum receive frame size
emac0.wdogto.write(|w|
w.bits(0) // ??? no use watchdog
);
// Set the MAC address
emac0.addr0l.write(|w|
w.addrlo().bits( mac.0[0] as u32 |
((mac.0[1] as u32) << 8) |
((mac.0[2] as u32) << 16) |
((mac.0[3] as u32) << 24))
);
emac0.addr0h.write(|w|
w.addrhi().bits( mac.0[4] as u16 |
((mac.0[5] as u16) << 8))
);
// Set MAC filtering options (?)
emac0.framefltr.write(|w|
w.hpf().bit(true) // Hash or Perfect Filter
//.hmc().bit(true) // Hash Multicast ???
.pm().bit(true) // Pass All Multicast
);
// Initialize hash table
emac0.hashtbll.write(|w| w.htl().bits(0));
emac0.hashtblh.write(|w| w.hth().bits(0));
emac0.flowctl.write(|w| w.bits(0)); // Disable flow control ???
emac0.txdladdr.write(|w| /*unsafe*/ {
w.bits((&mut self.tx.desc_buf[0] as *mut u32) as u32)
});
emac0.rxdladdr.write(|w| /*unsafe*/ {
w.bits((&mut self.rx.desc_buf[0] as *mut u32) as u32)
});
// Manage MAC transmission and reception
emac0.cfg.modify(|_, w|
w.re().bit(true) // Receiver Enable
.te().bit(true) // Transmiter Enable
);
// Manage DMA transmission and reception
emac0.dmaopmode.modify(|_, w|
w.sr().bit(true) // Start Receive
.st().bit(true) // Start Transmit
);
});
}
}
impl<'a, 'b> phy::Device<'a> for &'b mut Device {
type RxToken = RxToken<'a>;
type TxToken = TxToken<'a>;
fn capabilities(&self) -> phy::DeviceCapabilities {
let mut capabilities = phy::DeviceCapabilities::default();
capabilities.max_transmission_unit = 1500;
capabilities.max_burst_size = Some(ETH_RX_BUFFER_COUNT);
capabilities
}
fn receive(&mut self) -> Option<(RxToken, TxToken)> {
// Skip all queued packets with errors.
while self.rx.buf_owned() && !self.rx.buf_valid() {
self.rx.buf_release()
}
if !(self.rx.buf_owned() && self.tx.buf_owned()) {
return None
}
Some((RxToken(&mut self.rx), TxToken(&mut self.tx)))
}
fn transmit(&mut self) -> Option<TxToken> {
if !self.tx.buf_owned() {
return None
}
Some(TxToken(&mut self.tx))
}
}
pub struct RxToken<'a>(&'a mut RxRing);
impl<'a> phy::RxToken for RxToken<'a> {
fn consume<R, F>(self, _timestamp: Instant, f: F) -> Result<R>
where F: FnOnce(&mut [u8]) -> Result<R> {
let result = f(unsafe { self.0.buf_as_slice() });
self.0.buf_release();
result
}
}
pub struct TxToken<'a>(&'a mut TxRing);
impl<'a> phy::TxToken for TxToken<'a> {
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
where F: FnOnce(&mut [u8]) -> Result<R> {
let result = f(unsafe { self.0.buf_as_slice(len) });
self.0.buf_release();
result
}
}

BIN
firmware/src/logo.svg.gz Normal file

Binary file not shown.

516
firmware/src/main.rs Normal file
View File

@ -0,0 +1,516 @@
#![feature(const_fn, proc_macro_hygiene)]
#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(test), no_main)]
use cortex_m_rt::entry;
use core::fmt::{self, Write};
use smoltcp::time::Instant;
use smoltcp::wire::{IpCidr, IpAddress, EthernetAddress};
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
use cortex_m_semihosting::hio;
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ({
use core::fmt::Write;
write!($crate::UART0, $($arg)*).unwrap()
})
}
#[macro_export]
macro_rules! println {
($fmt:expr) => (print!(concat!($fmt, "\n")));
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}
#[cfg(not(test))]
#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647}
#[panic_handler]
pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! {
println!("{}", info);
let mut stdout = hio::hstdout().unwrap();
let _ = writeln!(stdout, "{}", info);
loop {}
}
mod board;
use self::board::{
gpio::Gpio,
systick::get_time,
};
mod ethmac;
mod command_parser;
use command_parser::{Command, ShowCommand, PwmSetup, PwmMode, PwmConfig};
mod session;
use self::session::{Session, SessionOutput};
mod ad7172;
mod pid;
mod tec;
use tec::{Tec, TecPin};
mod steinhart_hart;
use steinhart_hart as sh;
pub struct UART0;
impl fmt::Write for UART0 {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
let uart_0 = unsafe { &*tm4c129x::UART0::ptr() };
for c in s.bytes() {
while uart_0.fr.read().txff().bit() {}
uart_0.dr.write(|w| w.data().bits(c))
}
Ok(())
}
}
const TCP_RX_BUFFER_SIZE: usize = 256;
const TCP_TX_BUFFER_SIZE: usize = 8192;
macro_rules! create_socket_storage {
($rx_storage:ident, $tx_storage:ident) => (
let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE];
let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE];
)
}
macro_rules! create_socket {
($set:ident, $rx_storage:ident, $tx_storage:ident, $target:ident) => (
let tcp_rx_buffer = TcpSocketBuffer::new(&mut $rx_storage[..]);
let tcp_tx_buffer = TcpSocketBuffer::new(&mut $tx_storage[..]);
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
let $target = $set.add(tcp_socket);
)
}
const VCC: f32 = 3.3;
const PWM_PID_WIDTH: u16 = 0xffff;
const PWM_MAX: f32 = PWM_PID_WIDTH as f32;
const DEFAULT_PID_PARAMETERS: pid::Parameters = pid::Parameters {
kp: 0.5 * PWM_MAX,
ki: 0.05 * PWM_MAX,
kd: 0.45 * PWM_MAX,
output_min: 0.0,
output_max: PWM_MAX,
integral_min: 0.0,
integral_max: PWM_MAX,
};
const DEFAULT_SH_PARAMETERS: sh::Parameters = sh::Parameters {
a: 0.001_4,
b: 0.000_237,
c: 0.000_000_099,
parallel_r: 5_110.0, // Ohm (TODO: verify)
};
// TODO: maybe rename to `TECS`?
/// Number of TEC channels with four PWM channels each
pub const CHANNELS: usize = 2;
// TODO: maybe rename to `TecState`?
/// State per TEC channel
#[derive(Clone)]
struct ControlState {
/// Report data (time, data, temperature)
report: Option<(u64, i32, f32, Option<u16>)>,
pid_enabled: bool,
pid: pid::Controller,
sh: sh::Parameters,
}
#[cfg(not(test))]
#[entry]
fn main() -> ! {
let mut stdout = hio::hstdout().unwrap();
writeln!(stdout, "tecpak boot").unwrap();
board::init();
writeln!(stdout, "board initialized").unwrap();
let mut tec0 = Tec::tec0().setup(PWM_PID_WIDTH / 2, PWM_PID_WIDTH);
let mut tec1 = Tec::tec1().setup(PWM_PID_WIDTH / 2, PWM_PID_WIDTH);
println!(r#"
_ _
| | | |
/ _/___ _ __ _ __ __ _| |
| |/ _ \ /'__\| '_ \ / _` | |/ /
| | (/_/| |___| |_) | (_| | <
|_|\___\ \___/| .__/ \__,_|_|\_\
| |
|_| v1
"#);
// CSn
let pb4 = board::gpio::PB4.into_output();
// SCLK
let pb5 = board::gpio::PB5.into_output();
// MOSI
let pe4 = board::gpio::PE4.into_output();
// MISO
let pe5 = board::gpio::PE5.into_input();
// max 2 MHz = 0.5 us
let mut delay_fn = || for _ in 0..10 { cortex_m::asm::nop(); };
let spi = board::softspi::SyncSoftSpi::new(
board::softspi::SoftSpi::new(pb5, pe4, pe5),
&mut delay_fn
);
let mut adc = ad7172::Adc::new(spi, pb4).unwrap();
loop {
let r = adc.identify();
match r {
Err(e) =>
writeln!(stdout, "Cannot identify ADC: {:?}", e).unwrap(),
Ok(id) if id & 0xFFF0 == 0x00D0 => {
writeln!(stdout, "ADC id: {:04X}", id).unwrap();
break;
}
Ok(_id) => {
// This always happens on the first attempt. So retry silently
}
};
}
writeln!(stdout, "AD7172: setting checksum mode").unwrap();
adc.set_checksum_mode(ad7172::ChecksumMode::Crc).unwrap();
loop {
let r = adc.identify();
match r {
Err(e) =>
writeln!(stdout, "Cannot identify ADC: {:?}", e).unwrap(),
Ok(id) if id & 0xFFF0 == 0x00D0 => {
writeln!(stdout, "ADC id: {:04X}", id).unwrap();
break;
}
Ok(id) =>
writeln!(stdout, "Corrupt ADC id: {:04X}", id).unwrap(),
};
}
adc.set_sync_enable(false).unwrap();
// SENS0_{P,N}
adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap();
// SENS1_{P,N}
adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap();
let init_state = ControlState {
report: None,
// Start with disengaged PID to let user setup parameters first
pid_enabled: false,
pid: pid::Controller::new(DEFAULT_PID_PARAMETERS.clone()),
sh: DEFAULT_SH_PARAMETERS.clone(),
};
let mut states = [init_state.clone(), init_state.clone()];
let mut hardware_addr = EthernetAddress(board::get_mac_address());
writeln!(stdout, "MAC address: {}", hardware_addr).unwrap();
if hardware_addr.is_multicast() {
writeln!(stdout, "programmed MAC address is invalid, using default").unwrap();
hardware_addr = EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]);
}
let mut ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 1, 26), 24)];
println!("MAC {} IP {}", hardware_addr, ip_addrs[0]);
let mut neighbor_cache_storage = [None; 8];
let neighbor_cache = NeighborCache::new(&mut neighbor_cache_storage[..]);
let mut device = ethmac::Device::new();
unsafe { device.init(hardware_addr) };
let mut iface = EthernetInterfaceBuilder::new(&mut device)
.ethernet_addr(hardware_addr)
.neighbor_cache(neighbor_cache)
.ip_addrs(&mut ip_addrs[..])
.finalize();
create_socket_storage!(tcp_rx_storage0, tcp_tx_storage0);
create_socket_storage!(tcp_rx_storage1, tcp_tx_storage1);
create_socket_storage!(tcp_rx_storage2, tcp_tx_storage2);
create_socket_storage!(tcp_rx_storage3, tcp_tx_storage3);
create_socket_storage!(tcp_rx_storage4, tcp_tx_storage4);
create_socket_storage!(tcp_rx_storage5, tcp_tx_storage5);
create_socket_storage!(tcp_rx_storage6, tcp_tx_storage6);
create_socket_storage!(tcp_rx_storage7, tcp_tx_storage7);
let mut socket_set_entries: [_; 8] = Default::default();
let mut sockets = SocketSet::new(&mut socket_set_entries[..]);
create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, tcp_handle0);
create_socket!(sockets, tcp_rx_storage1, tcp_tx_storage1, tcp_handle1);
create_socket!(sockets, tcp_rx_storage2, tcp_tx_storage2, tcp_handle2);
create_socket!(sockets, tcp_rx_storage3, tcp_tx_storage3, tcp_handle3);
create_socket!(sockets, tcp_rx_storage4, tcp_tx_storage4, tcp_handle4);
create_socket!(sockets, tcp_rx_storage5, tcp_tx_storage5, tcp_handle5);
create_socket!(sockets, tcp_rx_storage6, tcp_tx_storage6, tcp_handle6);
create_socket!(sockets, tcp_rx_storage7, tcp_tx_storage7, tcp_handle7);
let mut sessions_handles = [
(Session::new(), tcp_handle0),
(Session::new(), tcp_handle1),
(Session::new(), tcp_handle2),
(Session::new(), tcp_handle3),
(Session::new(), tcp_handle4),
(Session::new(), tcp_handle5),
(Session::new(), tcp_handle6),
(Session::new(), tcp_handle7),
];
loop {
// ADC input
adc.data_ready()
.unwrap_or_else(|e| {
writeln!(stdout, "ADC error: {:?}", e).unwrap();
None
}).map(|channel| {
let now = get_time();
let data = adc.read_data().unwrap();
let state = &mut states[usize::from(channel)];
let voltage = VCC * (data as f32) / (0x7FFFFF as f32);
let temperature = state.sh.get_temperature(voltage);
let pwm_width = if state.pid_enabled {
let width = state.pid.update(temperature) as u16;
match channel {
0 => tec0.set(TecPin::ISet, width, PWM_PID_WIDTH),
1 => tec1.set(TecPin::ISet, width, PWM_PID_WIDTH),
_ => unreachable!(),
}
Some(width)
} else {
None
};
state.report = Some((now, data, temperature, pwm_width));
for (session, _) in sessions_handles.iter_mut() {
session.set_report_pending(channel.into());
}
});
for (session, tcp_handle) in sessions_handles.iter_mut() {
let socket = &mut *sockets.get::<TcpSocket>(*tcp_handle);
if !socket.is_open() {
if session.is_dirty() {
// Reset a previously uses session/socket
*session = Session::new();
}
socket.listen(23).unwrap()
}
if socket.may_recv() && socket.may_send() {
let output = socket.recv(|buf| session.feed(buf));
// TODO: use "{}" to display pretty errors
match output {
Ok(SessionOutput::Nothing) => {}
Ok(SessionOutput::Command(command)) => match command {
Command::Quit =>
socket.close(),
Command::Reporting(reporting) => {
let _ = writeln!(socket, "report={}", if reporting { "on" } else { "off" });
}
Command::Show(ShowCommand::Reporting) => {
let _ = writeln!(socket, "report={}", if session.reporting() { "on" } else { "off" });
}
Command::Show(ShowCommand::Input) => {
for (channel, state) in states.iter().enumerate() {
state.report.map(|(time, data, temp, pwm_width)| {
let _ = write!(
socket, "t={} temp{}={} raw{}=0x{:06X}",
time, channel, temp, channel, data
);
pwm_width.map(|width| {
let _ = write!(
socket, " pwm{}=0x{:04X}",
channel, width
);
});
let _ = writeln!(socket, "");
});
}
}
Command::Show(ShowCommand::Pid) => {
for (channel, state) in states.iter().enumerate() {
let _ = writeln!(socket, "PID settings for channel {}", channel);
let pid = &state.pid;
let _ = writeln!(socket, "- target={:.4}", pid.get_target());
let p = pid.get_parameters();
macro_rules! out {
($p: tt) => {
let _ = writeln!(socket, "- {}={:.4}", stringify!($p), p.$p);
};
}
out!(kp);
out!(ki);
out!(kd);
out!(output_min);
out!(output_max);
out!(integral_min);
out!(integral_max);
let _ = writeln!(socket, "");
}
}
Command::Show(ShowCommand::Pwm) => {
for (channel, state) in states.iter().enumerate() {
let _ = writeln!(
socket, "channel {}: PID={}",
channel,
if state.pid_enabled { "engaged" } else { "disengaged" }
);
for pin in TecPin::VALID_VALUES {
let (width, total) = match channel {
0 => tec0.get(*pin),
1 => tec1.get(*pin),
_ => unreachable!(),
};
let _ = writeln!(socket, "- {}={}/{}", pin, width, total);
}
let _ = writeln!(socket, "");
}
}
Command::Show(ShowCommand::SteinhartHart) => {
for (channel, state) in states.iter().enumerate() {
let _ = writeln!(
socket, "channel {}: Steinhart-Hart equation parameters",
channel,
);
let _ = writeln!(socket, "- a={}", state.sh.a);
let _ = writeln!(socket, "- b={}", state.sh.b);
let _ = writeln!(socket, "- c={}", state.sh.c);
let _ = writeln!(socket, "- parallel_r={}", state.sh.parallel_r);
let _ = writeln!(socket, "");
}
}
Command::Show(ShowCommand::PostFilter) => {
for (channel, _) in states.iter().enumerate() {
match adc.get_postfilter(channel as u8).unwrap() {
Some(filter) => {
let _ = writeln!(
socket, "channel {}: postfilter={:.2} SPS",
channel, filter.output_rate().unwrap()
);
}
None => {
let _ = writeln!(
socket, "channel {}: no postfilter",
channel
);
}
}
}
}
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Pid) } => {
states[channel].pid_enabled = true;
let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel);
}
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Manual(config))} => {
states[channel].pid_enabled = false;
let PwmConfig { width, total } = config;
match channel {
0 => tec0.set(TecPin::ISet, width, total),
1 => tec1.set(TecPin::ISet, width, total),
_ => unreachable!(),
}
let _ = writeln!(
socket, "channel {}: PWM duty cycle manually set to {}/{}",
channel, config.width, config.total
);
}
Command::Pwm { channel, setup } => {
let (pin, config) = match setup {
PwmSetup::ISet(_) =>
// Handled above
unreachable!(),
PwmSetup::MaxIPos(config) =>
(TecPin::MaxIPos, config),
PwmSetup::MaxINeg(config) =>
(TecPin::MaxINeg, config),
PwmSetup::MaxV(config) =>
(TecPin::MaxV, config),
};
let PwmConfig { width, total } = config;
match channel {
0 => tec0.set(pin, width, total),
1 => tec1.set(pin, width, total),
_ => unreachable!(),
}
let _ = writeln!(
socket, "channel {}: PWM {} reconfigured to {}/{}",
channel, pin, width, total
);
}
Command::Pid { channel, parameter, value } => {
let pid = &mut states[channel].pid;
use command_parser::PidParameter::*;
match parameter {
Target =>
pid.set_target(value),
KP =>
pid.update_parameters(|parameters| parameters.kp = value),
KI =>
pid.update_parameters(|parameters| parameters.ki = value),
KD =>
pid.update_parameters(|parameters| parameters.kd = value),
OutputMin =>
pid.update_parameters(|parameters| parameters.output_min = value),
OutputMax =>
pid.update_parameters(|parameters| parameters.output_max = value),
IntegralMin =>
pid.update_parameters(|parameters| parameters.integral_min = value),
IntegralMax =>
pid.update_parameters(|parameters| parameters.integral_max = value),
}
pid.reset();
let _ = writeln!(socket, "PID parameter updated");
}
Command::SteinhartHart { channel, parameter, value } => {
let sh = &mut states[channel].sh;
use command_parser::ShParameter::*;
match parameter {
A => sh.a = value,
B => sh.b = value,
C => sh.c = value,
ParallelR => sh.parallel_r = value,
}
let _ = writeln!(socket, "Steinhart-Hart equation parameter updated");
}
Command::PostFilter { channel, rate } => {
let filter = ad7172::PostFilter::closest(rate);
match filter {
Some(filter) => {
adc.set_postfilter(channel as u8, Some(filter)).unwrap();
let _ = writeln!(
socket, "channel {}: postfilter set to {:.2} SPS",
channel, filter.output_rate().unwrap()
);
}
None => {
let _ = writeln!(socket, "Unable to choose postfilter");
}
}
}
}
Ok(SessionOutput::Error(e)) => {
let _ = writeln!(socket, "Command error: {:?}", e);
}
Err(_) => {}
}
}
if socket.may_send() {
if let Some(channel) = session.is_report_pending() {
states[channel].report.map(|(time, data, temp, pwm_width)| {
let _ = write!(
socket, "t={} temp{}={} raw{}=0x{:06X}",
time, channel, temp, channel, data
);
pwm_width.map(|width| {
let _ = write!(
socket, " pwm{}=0x{:04X}",
channel, width
);
});
let _ = writeln!(socket, "");
});
session.mark_report_sent(channel);
}
}
}
match iface.poll(&mut sockets, Instant::from_millis((get_time() / 1000) as i64)) {
Ok(_) => (),
Err(e) => println!("poll error: {}", e)
}
}
}

122
firmware/src/pid.rs Normal file
View File

@ -0,0 +1,122 @@
#[derive(Clone, Copy)]
pub struct Parameters {
pub kp: f32,
pub ki: f32,
pub kd: f32,
pub output_min: f32,
pub output_max: f32,
pub integral_min: f32,
pub integral_max: f32
}
#[derive(Clone)]
pub struct Controller {
parameters: Parameters,
target: f32,
integral: f32,
last_input: Option<f32>
}
impl Controller {
pub const fn new(parameters: Parameters) -> Controller {
Controller {
parameters: parameters,
target: 0.0,
last_input: None,
integral: 0.0
}
}
pub fn update(&mut self, input: f32) -> f32 {
let error = self.target - input;
let p = self.parameters.kp * error;
self.integral += error;
if self.integral < self.parameters.integral_min {
self.integral = self.parameters.integral_min;
}
if self.integral > self.parameters.integral_max {
self.integral = self.parameters.integral_max;
}
let i = self.parameters.ki * self.integral;
let d = match self.last_input {
None => 0.0,
Some(last_input) => self.parameters.kd * (last_input - input)
};
self.last_input = Some(input);
let mut output = p + i + d;
if output < self.parameters.output_min {
output = self.parameters.output_min;
}
if output > self.parameters.output_max {
output = self.parameters.output_max;
}
output
}
pub fn get_target(&self) -> f32 {
self.target
}
pub fn set_target(&mut self, target: f32) {
self.target = target;
}
pub fn get_parameters(&self) -> &Parameters {
&self.parameters
}
pub fn update_parameters<F: FnOnce(&mut Parameters)>(&mut self, f: F) {
f(&mut self.parameters);
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.integral = 0.0;
self.last_input = None;
}
}
#[cfg(test)]
mod test {
use super::*;
const PARAMETERS: Parameters = Parameters {
kp: 0.055,
ki: 0.005,
kd: 0.04,
output_min: -10.0,
output_max: 10.0,
integral_min: -100.0,
integral_max: 100.0,
};
#[test]
fn test_controller() {
const DEFAULT: f32 = 0.0;
const TARGET: f32 = 1234.56;
const ERROR: f32 = 0.01;
const DELAY: usize = 10;
let mut pid = Controller::new(PARAMETERS.clone());
pid.set_target(TARGET);
let mut values = [DEFAULT; DELAY];
let mut t = 0;
let mut total_t = 0;
let target = (TARGET - ERROR)..=(TARGET + ERROR);
while !values.iter().all(|value| target.contains(value)) {
let next_t = (t + 1) % DELAY;
// Feed the oldest temperature
let output = pid.update(values[next_t]);
// Overwrite oldest with previous temperature + output
values[next_t] = values[t] + output;
t = next_t;
total_t += 1;
}
dbg!(values[t], total_t);
}
}

138
firmware/src/session.rs Normal file
View File

@ -0,0 +1,138 @@
use core::ops::Deref;
use super::command_parser::{Command, Error as ParserError};
use super::CHANNELS;
const MAX_LINE_LEN: usize = 64;
struct LineReader {
buf: [u8; MAX_LINE_LEN],
pos: usize,
}
impl LineReader {
pub fn new() -> Self {
LineReader {
buf: [0; MAX_LINE_LEN],
pos: 0,
}
}
pub fn feed(&mut self, c: u8) -> Option<LineResult> {
if c == 13 || c == 10 {
// Enter
if self.pos > 0 {
let len = self.pos;
self.pos = 0;
Some(LineResult {
buf: self.buf.clone(),
len,
})
} else {
None
}
} else if self.pos < self.buf.len() {
// Add input
self.buf[self.pos] = c;
self.pos += 1;
None
} else {
// Buffer is full, ignore
None
}
}
}
pub struct LineResult {
buf: [u8; MAX_LINE_LEN],
len: usize,
}
impl Deref for LineResult {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.buf[..self.len]
}
}
pub enum SessionOutput {
Nothing,
Command(Command),
Error(ParserError),
}
impl From<Result<Command, ParserError>> for SessionOutput {
fn from(input: Result<Command, ParserError>) -> Self {
input.map(SessionOutput::Command)
.unwrap_or_else(SessionOutput::Error)
}
}
pub struct Session {
reader: LineReader,
reporting: bool,
report_pending: [bool; CHANNELS],
}
impl Session {
pub fn new() -> Self {
Session {
reader: LineReader::new(),
reporting: false,
report_pending: [false; CHANNELS],
}
}
pub fn is_dirty(&self) -> bool {
self.reader.pos > 0
}
pub fn reporting(&self) -> bool {
self.reporting
}
pub fn set_report_pending(&mut self, channel: usize) {
if self.reporting {
self.report_pending[channel] = true;
}
}
pub fn is_report_pending(&self) -> Option<usize> {
if ! self.reporting {
None
} else {
self.report_pending.iter()
.enumerate()
.fold(None, |result, (channel, report_pending)| {
result.or_else(|| {
if *report_pending { Some(channel) } else { None }
})
})
}
}
pub fn mark_report_sent(&mut self, channel: usize) {
self.report_pending[channel] = false;
}
pub fn feed(&mut self, buf: &[u8]) -> (usize, SessionOutput) {
let mut buf_bytes = 0;
for (i, b) in buf.iter().enumerate() {
buf_bytes = i + 1;
let line = self.reader.feed(*b);
match line {
Some(line) => {
let command = Command::parse(&line);
match command {
Ok(Command::Reporting(reporting)) => {
self.reporting = reporting;
}
_ => {}
}
return (buf_bytes, command.into());
}
None => {}
}
}
(buf_bytes, SessionOutput::Nothing)
}
}

View File

@ -0,0 +1,30 @@
use libm::F32Ext;
/// Steinhart-Hart equation parameters
#[derive(Clone, Debug)]
pub struct Parameters {
pub a: f32,
pub b: f32,
pub c: f32,
/// Parallel resistance
///
/// Not truly part of the equation but required to calculate
/// resistance from voltage.
pub parallel_r: f32,
}
impl Parameters {
/// Perform the voltage to temperature conversion.
///
/// Result unit: Kelvin
///
/// TODO: verify
pub fn get_temperature(&self, voltage: f32) -> f32 {
let r = self.parallel_r * voltage;
let ln_r = r.abs().ln();
let inv_temp = self.a +
self.b * ln_r +
self.c * ln_r * ln_r * ln_r;
1.0 / inv_temp
}
}

BIN
firmware/src/style.css.gz Normal file

Binary file not shown.

128
firmware/src/tec.rs Normal file
View File

@ -0,0 +1,128 @@
use core::fmt;
use crate::board::pwm::{self, PwmChannel, PwmPeripheral};
use crate::board::gpio::{Gpio, GpioOutput, PP2, PP3, PK1, PQ4};
use embedded_hal::digital::v2::OutputPin;
#[derive(Clone, Copy, Debug)]
pub enum TecPin {
ISet,
MaxIPos,
MaxINeg,
MaxV,
}
impl TecPin {
pub const VALID_VALUES: &'static [TecPin] = &[
TecPin::ISet,
TecPin::MaxIPos,
TecPin::MaxINeg,
TecPin::MaxV,
];
}
impl fmt::Display for TecPin {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
TecPin::ISet =>
"i_set".fmt(fmt),
TecPin::MaxIPos =>
"max_i_pos".fmt(fmt),
TecPin::MaxINeg =>
"max_i_neg".fmt(fmt),
TecPin::MaxV =>
"max_v".fmt(fmt),
}
}
}
fn setup_shdn<G>(gpio: G) -> GpioOutput<G>
where
G: Gpio,
GpioOutput<G>: OutputPin,
{
let mut pin = gpio.into_output();
// keep off until first use
let _ = pin.set_low();
pin
}
fn setup_freq<G>(gpio: G)
where
G: Gpio,
GpioOutput<G>: OutputPin,
{
let mut pin = gpio.into_output();
// Switching Frequency Select
// high: 1 MHz, low: 500 kHz
let _ = pin.set_high();
}
/// Thermo-Electric Cooling device controlled through four PWM
/// channels
pub struct Tec<MaxIPos: PwmChannel, MaxINeg: PwmChannel, ISet: PwmChannel, MaxV: PwmChannel, SHDN: OutputPin> {
max_i_pos: MaxIPos,
max_i_neg: MaxINeg,
i_set: ISet,
max_v: MaxV,
shdn: SHDN,
}
impl Tec<pwm::T2CCP0, pwm::T2CCP1, pwm::T3CCP0, pwm::T3CCP1, GpioOutput<PP2>> {
pub fn tec0() -> Self {
let (max_i_pos, max_i_neg) = tm4c129x::TIMER2::split();
let (i_set, max_v) = tm4c129x::TIMER3::split();
let shdn = setup_shdn(PP2);
setup_freq(PK1);
Tec { max_i_pos, max_i_neg, i_set, max_v, shdn }
}
}
impl Tec<pwm::T4CCP0, pwm::T4CCP1, pwm::T5CCP0, pwm::T5CCP1, GpioOutput<PP3>> {
pub fn tec1() -> Self {
let (max_i_pos, max_i_neg) = tm4c129x::TIMER4::split();
let (i_set, max_v) = tm4c129x::TIMER5::split();
let shdn = setup_shdn(PP3);
setup_freq(PQ4);
Tec { max_i_pos, max_i_neg, i_set, max_v, shdn }
}
}
impl<MaxIPos: PwmChannel, MaxINeg: PwmChannel, ISet: PwmChannel, MaxV: PwmChannel, SHDN: OutputPin> Tec<MaxIPos, MaxINeg, ISet, MaxV, SHDN> {
pub fn setup(mut self, iset_width: u16, max: u16) -> Self {
self.max_i_pos.set(max, max);
self.max_i_neg.set(max, max);
self.max_v.set(max, max);
self.i_set.set(iset_width, max);
self
}
pub fn get(&mut self, pin: TecPin) -> (u16, u16) {
match pin {
TecPin::MaxIPos =>
self.max_i_pos.get(),
TecPin::MaxINeg =>
self.max_i_neg.get(),
TecPin::ISet =>
self.i_set.get(),
TecPin::MaxV =>
self.max_v.get(),
}
}
pub fn set(&mut self, pin: TecPin, width: u16, total: u16) {
match pin {
TecPin::MaxIPos =>
self.max_i_pos.set(width, total),
TecPin::MaxINeg =>
self.max_i_neg.set(width, total),
TecPin::ISet => {
self.i_set.set(width, total);
// enable on first use
let _ = self.shdn.set_high();
}
TecPin::MaxV =>
self.max_v.set(width, total),
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

28
shell.nix Normal file
View File

@ -0,0 +1,28 @@
let
mozillaOverlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
pkgs = import <nixpkgs> { overlays = [ mozillaOverlay ]; };
in
with pkgs;
let
project = callPackage ./default.nix {};
in
with project;
stdenv.mkDerivation {
name = "armdev-env";
buildInputs = with rustPlatform.rust; [
rustc cargo cargo-xbuild
rustcSrc
pkgsCross.arm-embedded.stdenv.cc
openocd
];
# Set Environment Variables
RUST_BACKTRACE = 1;
XARGO_RUST_SRC = "${rustcSrc}/src";
RUST_COMPILER_RT_ROOT = "${rustcSrc}/src/llvm-project/compiler-rt";
shellHook = ''
cd firmware
echo "Run 'cargo xbuild --release' to build."
'';
}