application: Store pace in the model and show it in its own view.
[gps-watch.git] / src / application / main.rs
1 /*
2  * Copyright (c) 2019 Tilman Sauerbeck (tilman at code-monkey de)
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23
24 #![no_std]
25 #![no_main]
26 #[link(name="libcommon.rlib")]
27
28 extern crate common;
29
30 mod uart0;
31 mod button;
32 mod model;
33 mod views;
34
35 use common::buffer::Buffer;
36 use common::ringbuf::Ringbuf;
37 use common::clock;
38 use common::systick;
39 use common::port;
40 use common::gpio;
41 use common::nvic;
42 use common::i2c;
43 use common::spi;
44 use common::uart;
45 use common::usb_serial;
46 use common::display;
47 use common::gps;
48 use common::screen;
49 use common::mx25l::Mx25l;
50 use common::shell::Shell;
51 use common::logger::Logger;
52
53 extern {
54     fn enable_interrupts();
55
56     static mut cdc_tx_buf: Buffer;
57 }
58
59 #[derive(Clone, Copy, PartialEq)]
60 enum View {
61     Time,
62     Distance,
63     Pace,
64 }
65
66 struct Timer {
67     state: u32,
68     delay_ms: u32,
69     last_update_ticks: u32,
70 }
71
72 impl Timer {
73     pub fn new(delay_ms: u32) -> Timer {
74         Timer {
75             state: 0,
76             delay_ms: delay_ms,
77             last_update_ticks: systick::now(),
78         }
79     }
80
81     pub fn update<F>(&mut self, func: F)
82         where F: FnOnce(u32) -> u32
83     {
84         if systick::has_timeout_ms(self.last_update_ticks, self.delay_ms) {
85             self.state = func(self.state);
86
87             self.last_update_ticks = systick::now();
88         }
89     }
90 }
91
92 fn next_view(view: View) -> View {
93     match view {
94         View::Time => View::Distance,
95         View::Distance => View::Pace,
96         View::Pace => View::Time,
97     }
98 }
99
100 fn previous_view(view: View) -> View {
101     match view {
102         View::Time => View::Pace,
103         View::Distance => View::Time,
104         View::Pace => View::Distance,
105     }
106 }
107
108 fn reset_requested() -> bool {
109     let pta1 = gpio::get(gpio::GPIOA) & (1 << 1);
110     let pte25 = gpio::get(gpio::GPIOE) & (1 << 25);
111
112     (pta1 | pte25) == 0
113 }
114
115 #[inline(never)]
116 #[panic_handler]
117 fn panic(_info: &core::panic::PanicInfo) -> ! {
118     loop {
119         if reset_requested() {
120             nvic::system_reset();
121         }
122     }
123 }
124
125 fn configure_push_buttons() {
126     // Configure lower right push button.
127     gpio::set_direction(gpio::GPIOA, 1 << 1, gpio::Direction::Input);
128     port::set_af(port::PORTA, 1, 1);
129     port::set_pull(port::PORTA, 1, port::Pull::Up);
130
131     // Configure upper right push button.
132     gpio::set_direction(gpio::GPIOA, 1 << 12, gpio::Direction::Input);
133     port::set_af(port::PORTA, 12, 1);
134     port::set_pull(port::PORTA, 12, port::Pull::Up);
135
136     // Configure lower left push button.
137     gpio::set_direction(gpio::GPIOE, 1 << 24, gpio::Direction::Input);
138     port::set_af(port::PORTE, 24, 1);
139     port::set_pull(port::PORTE, 24, port::Pull::Up);
140
141     // Configure upper left push button.
142     gpio::set_direction(gpio::GPIOE, 1 << 25, gpio::Direction::Input);
143     port::set_af(port::PORTE, 25, 1);
144     port::set_pull(port::PORTE, 25, port::Pull::Up);
145
146     // Configure middle left push button.
147     gpio::set_direction(gpio::GPIOE, 1 << 31, gpio::Direction::Input);
148     port::set_af(port::PORTE, 31, 1);
149     port::set_pull(port::PORTE, 31, port::Pull::Up);
150 }
151
152 fn uart0_try_read() -> Option<u8> {
153     extern {
154         static mut uart0_rx_buf: Ringbuf;
155     }
156
157     unsafe {
158         if uart0_rx_buf.is_empty() {
159             None
160         } else {
161             Some(uart0_rx_buf.read())
162         }
163     }
164 }
165
166 #[no_mangle]
167 pub unsafe extern "C" fn _start() -> ! {
168     clock::configure();
169     clock::enable_osc0();
170     systick::init();
171     port::init();
172
173     // Configure pins for I2C0.
174     port::set_af(port::PORTC, 8, 2);
175     port::set_af(port::PORTC, 9, 2);
176
177     i2c::configure(i2c::I2C0);
178
179     nvic::disable_irq(8); // I2C0
180
181     // Configure pin for the display's reset line.
182     gpio::set_direction(gpio::GPIOB, 1 << 16, gpio::Direction::Output);
183     port::set_af(port::PORTB, 16, 1);
184
185     // Configure pin for the MX25L's chip select line.
186     gpio::set_direction(gpio::GPIOD, 1 << 0, gpio::Direction::Output);
187     port::set_af(port::PORTD, 0, 1);
188     gpio::set(gpio::GPIOD, 1 << 0);
189
190     // Configure pins for SPI0.
191     port::set_af(port::PORTD, 1, 2);
192     port::set_af(port::PORTD, 2, 5);
193     port::set_af(port::PORTD, 3, 5);
194
195     spi::configure(spi::SPI0);
196
197     nvic::disable_irq(10); // SPI0
198
199     // Configure pins for UART0.
200     port::set_af(port::PORTE, 20, 4);
201     port::set_af(port::PORTE, 21, 4);
202
203     // Configure pin for the GPS's reset line.
204     gpio::set_direction(gpio::GPIOB, 1 << 1, gpio::Direction::Output);
205     port::set_af(port::PORTB, 1, 1);
206
207     configure_push_buttons();
208
209     enable_interrupts();
210
211     usb_serial::init(0xf055, 0x635d);
212
213     cdc_tx_buf.write(b"\n");
214     cdc_tx_buf.flush();
215
216     let mut display = display::Display::new(gpio::GPIOB, 1 << 16, 0x3c);
217
218     display.init();
219     display.clear();
220
221     let mut screen = screen::Screen::new();
222
223     // Hold GPS in reset while configuring its UART.
224     gpio::clear(gpio::GPIOB, 1);
225     systick::delay_ms(50);
226     uart::configure(uart::UART0);
227     systick::delay_ms(50);
228     gpio::set(gpio::GPIOB, 1);
229
230     nvic::enable_irq(12); // UART0
231
232     let mut shell = Shell::new(&mut cdc_tx_buf);
233
234     let mut mx25l = Mx25l::new(gpio::GPIOD, 1 << 0);
235
236     let mut logger = Logger::new(&mut mx25l);
237     logger.init();
238
239     let mut pta12 = button::Button::new(gpio::GPIOA, 1 << 12);
240     let mut pte24 = button::Button::new(gpio::GPIOE, 1 << 24);
241     let mut pta01 = button::Button::new(gpio::GPIOA, 1 << 1);
242     let mut is_recording = false;
243
244     let mut gps = gps::Gps::new();
245
246     let mut gps_has_fix = false;
247     let mut gps_has_fix_ticks = 0;
248
249     let mut heart_icon_timer = Timer::new(1000);
250     let mut gps_icon_timer = Timer::new(500);
251
252     let mut prev_tap = gps::TimeAndPos::new();
253
254     let mut view = View::Time;
255
256     let mut time_view = views::TimeView::new();
257     let mut distance_view = views::DistanceView::new();
258     let mut pace_view = views::PaceView::new();
259
260     let mut model = model::Model::new();
261
262     loop {
263         let mut tap = gps::TimeAndPos::new();
264         let old_gps_has_fix = gps_has_fix;
265
266         while gps.update(&mut tap, uart0_try_read) {
267             if is_recording {
268                 logger.log(&prev_tap, &tap);
269
270                 model.update(model::Field::Distance(logger.total_distance_cm));
271                 model.update(model::Field::Pace(logger.pace_s));
272             }
273
274             model.update(model::Field::UnixTime(tap.unix_time));
275
276             prev_tap = tap;
277
278             gps_has_fix = true;
279             gps_has_fix_ticks = systick::now();
280         }
281
282         // Did GPS fix information expire?
283         if gps_has_fix && systick::has_timeout_ms(gps_has_fix_ticks, 1500) {
284             gps_has_fix = false;
285         }
286
287         if gps_has_fix && !old_gps_has_fix {
288             display.show_icon(display::Icon::SatelliteBody);
289             display.show_icon(display::Icon::SatelliteWave1);
290             display.show_icon(display::Icon::SatelliteWave2);
291         } else if !gps_has_fix {
292             gps_icon_timer.update(|state| {
293                 if state == 1 {
294                     display.show_icon(display::Icon::SatelliteWave1);
295                     2
296                 } else if state == 2 {
297                     display.show_icon(display::Icon::SatelliteWave2);
298                     3
299                 } else if state == 3 {
300                     display.hide_icon(display::Icon::SatelliteBody);
301                     display.hide_icon(display::Icon::SatelliteWave1);
302                     display.hide_icon(display::Icon::SatelliteWave2);
303                     0
304                 } else {
305                     display.show_icon(display::Icon::SatelliteBody);
306                     1
307                 }
308             });
309         }
310
311         match view {
312             View::Time => {
313                 if time_view.draw(&mut screen, &mut model) {
314                     display.draw(&screen);
315                 }
316             },
317             View::Distance => {
318                 if distance_view.draw(&mut screen, &mut model) {
319                     display.draw(&screen);
320                 }
321             },
322             View::Pace => {
323                 if pace_view.draw(&mut screen, &mut model) {
324                     display.draw(&screen);
325                 }
326             },
327         }
328
329         heart_icon_timer.update(|state| {
330             if state == 1 {
331                 display.hide_icon(display::Icon::Heart);
332                 0
333             } else {
334                 display.show_icon(display::Icon::Heart);
335                 1
336             }
337         });
338
339         shell.update(&mut logger);
340
341         if pta12.has_been_held_for_ms(1500) {
342             is_recording = !is_recording;
343
344             if is_recording {
345                 logger.start_recording(&prev_tap);
346
347                 model.reset();
348
349                 view = View::Distance;
350             } else {
351                 logger.stop_recording(&prev_tap);
352
353                 view = View::Time;
354             }
355         }
356
357         let new_view = if pte24.has_been_held_for_ms(250) {
358             previous_view(view)
359         } else if pta01.has_been_held_for_ms(250) {
360             next_view(view)
361         } else {
362             view
363         };
364
365         if view != new_view {
366             view = new_view;
367
368             match view {
369                 View::Time => time_view.invalidate(),
370                 View::Distance => distance_view.invalidate(),
371                 View::Pace => pace_view.invalidate(),
372             }
373         }
374
375         if reset_requested() {
376             nvic::system_reset();
377         }
378     }
379 }