Skip to main content

capsules_extra/
seven_segment.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Provides userspace access to 7 segment digit displays.
6//!
7//! This capsule was developed using the following components:
8//! - Microbit_v2
9//! - Edge Connector Breakout Board for Microbit (PPMB00126)
10//! - 7 segment display with 4 digits (3461BS-1)
11//! - breadboard, 220 ohms resistances and jump wires
12//!
13//! Usage
14//! -----
15//!
16//! Example of use for a display with 4 digits and the Microbit:
17//! Microbit Pins: <https://tech.microbit.org/hardware/schematic/>
18//! 4 digit 7 segment display pinout: <https://www.dotnetlovers.com/images/4digit7segmentdisplay85202024001AM.jpg>
19//!
20//! ```rust,ignore
21//! const NUM_DIGITS: usize = 4;
22//! const DIGITS: [Pin; 4] = [Pin::P1_02, Pin::P0_12, Pin::P0_30, Pin::P0_09]; // [D1, D2, D3, D4]
23//! const SEGMENTS: [Pin; 7] = [
24//!     Pin::P0_02, // A
25//!     Pin::P0_03, // B
26//!     Pin::P0_04, // C
27//!     Pin::P0_31, // D
28//!     Pin::P0_28, // E
29//!     Pin::P0_10, // F
30//!     Pin::P1_05, // G
31//! ];
32//! const DOT: Pin = Pin::P0_11;
33//!
34//! let segment_array = static_init!(
35//!     [&'static nrf52::gpio::GPIOPin<'static>; 8],
36//!     [
37//!         static_init!(
38//!             &'static nrf52::gpio::GPIOPin<'static>,
39//!             &nrf52833_peripherals.gpio_port[SEGMENTS[0]]
40//!         ),
41//!         static_init!(
42//!             &'static nrf52::gpio::GPIOPin<'static>,
43//!             &nrf52833_peripherals.gpio_port[SEGMENTS[1]]
44//!         ),
45//!         static_init!(
46//!             &'static nrf52::gpio::GPIOPin<'static>,
47//!             &nrf52833_peripherals.gpio_port[SEGMENTS[2]]
48//!         ),
49//!         static_init!(
50//!             &'static nrf52::gpio::GPIOPin<'static>,
51//!             &nrf52833_peripherals.gpio_port[SEGMENTS[3]]
52//!         ),
53//!         static_init!(
54//!             &'static nrf52::gpio::GPIOPin<'static>,
55//!             &nrf52833_peripherals.gpio_port[SEGMENTS[4]]
56//!         ),
57//!         static_init!(
58//!             &'static nrf52::gpio::GPIOPin<'static>,
59//!             &nrf52833_peripherals.gpio_port[SEGMENTS[5]]
60//!         ),
61//!         static_init!(
62//!             &'static nrf52::gpio::GPIOPin<'static>,
63//!             &nrf52833_peripherals.gpio_port[SEGMENTS[6]]
64//!         ),
65//!         static_init!(
66//!             &'static nrf52::gpio::GPIOPin<'static>,
67//!             &nrf52833_peripherals.gpio_port[DOT]
68//!         ),
69//!     ]
70//! );
71//!
72//! let digit_array = static_init!(
73//!     [&'static nrf52::gpio::GPIOPin<'static>; 4],
74//!     [
75//!         static_init!(
76//!             &'static nrf52::gpio::GPIOPin<'static>,
77//!             &nrf52833_peripherals.gpio_port[DIGITS[0]]
78//!         ),
79//!         static_init!(
80//!             &'static nrf52::gpio::GPIOPin<'static>,
81//!             &nrf52833_peripherals.gpio_port[DIGITS[1]]
82//!         ),
83//!         static_init!(
84//!             &'static nrf52::gpio::GPIOPin<'static>,
85//!             &nrf52833_peripherals.gpio_port[DIGITS[2]]
86//!         ),
87//!         static_init!(
88//!             &'static nrf52::gpio::GPIOPin<'static>,
89//!             &nrf52833_peripherals.gpio_port[DIGITS[3]]
90//!         ),
91//!     ]
92//! );
93//!
94//! let buffer = static_init!([u8; 4], [0; 4]);
95//!
96//! let digit_display = static_init!(
97//!     capsules::digits::DigitsDriver<
98//!         'static,
99//!         nrf52::gpio::GPIOPin<'static>,
100//!         capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>,
101//!     >,
102//!     capsules::digits::DigitsDriver::new(
103//!         segment_array,
104//!         digit_array,
105//!         buffer,
106//!         virtual_alarm_digit,
107//!         kernel::hil::gpio::ActivationMode::ActiveLow,
108//!         kernel::hil::gpio::ActivationMode::ActiveHigh,
109//!         60
110//!     ),
111//! );
112//! ```
113//!
114//! virtual_alarm_digit.set_alarm_client(digit_display);
115//!
116//! digit_display.init();
117//!
118//!
119//! Syscall Interface
120//! -----------------
121//!
122//! ### Command
123//!
124//! All operations are synchronous, so this capsule only uses the `command`
125//! syscall.
126//!
127//! #### `command_num`
128//!
129//! - `0`: Driver Check.
130//!   - `data1`: Unused.
131//!   - `data2`: Unused.
132//!   - Return: Number of digits.
133//! - `1`: Prints one digit at the requested position.
134//!   - `data1`: The position of the digit. Starts at 1.
135//!   - `data2`: The digit to be represented, from 0 to 9.
136//!   - Return: `Ok(())` if the digit index was valid, `INVAL` otherwise.
137//! - `2`: Clears all digits currently being displayed.
138//!   - `data1`: Unused.
139//!   - `data2`: Unused.
140//! - `3`: Print a dot at the requested digit position.
141//!   - `data1`: The position of the dot. Starts at 1.
142//!   - Return: `Ok(())` if the index was valid, `INVAL` otherwise.
143//! - `4`: Print a custom pattern for a digit on a certain position.
144//!   - `data1`: The position of the digit. Starts at 1.
145//!   - `data2`: The custom pattern to be represented.
146//!   - Return: `Ok(())` if the index was valid, `INVAL` otherwise.
147//! - `5`: Return the number of digits on the display being used.
148//!   - `data1`: Unused.
149//!   - `data2`: Unused.
150
151use core::cell::Cell;
152
153use kernel::ErrorCode;
154use kernel::ProcessId;
155use kernel::hil::gpio::{ActivationMode, Pin};
156use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks};
157use kernel::syscall::{CommandReturn, SyscallDriver};
158use kernel::utilities::cells::TakeCell;
159
160/// Syscall driver number.
161use capsules_core::driver;
162pub const DRIVER_NUM: usize = driver::NUM::SevenSegment as usize;
163
164/// Digit patterns
165//
166//      A
167//      _
168//   F |_| B       center = G
169//   E |_| C . Dp
170//      D
171//
172const DIGITS: [u8; 10] = [
173    // pattern: 0bDpGFEDCBA
174    0b00111111, // 0
175    0b00000110, // 1
176    0b01011011, // 2
177    0b01001111, // 3
178    0b01100110, // 4
179    0b01101101, // 5
180    0b01111101, // 6
181    0b00100111, // 7
182    0b01111111, // 8
183    0b01101111, // 9
184];
185
186/// Holds an array of digits and an array of segments for each digit.
187pub struct SevenSegmentDriver<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> {
188    /// An array of 8 segment pins (7 for digit segments and one dot segment)
189    segments: &'a [&'a P; 8],
190    /// An array of `NUM_DIGITS` digit pins, each one corresponding to one digit on the display
191    /// For each digit selected, a pattern of lit and unlit segments will be represented
192    digits: &'a [&'a P; NUM_DIGITS],
193    /// A buffer which contains the patterns displayed for each digit
194    /// Each element of the buffer array represents the pattern for one digit, and
195    /// is a sequence of bits that have the value 1 for a lit segment and the value 0 for
196    /// an unlit segment.
197    buffer: TakeCell<'a, [u8; NUM_DIGITS]>,
198    alarm: &'a A,
199    current_digit: Cell<usize>,
200    /// How fast the driver should switch between digits (ms)
201    timing: u8,
202    segment_activation: ActivationMode,
203    digit_activation: ActivationMode,
204}
205
206impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> SevenSegmentDriver<'a, P, A, NUM_DIGITS> {
207    pub fn new(
208        segments: &'a [&'a P; 8],
209        digits: &'a [&'a P; NUM_DIGITS],
210        buffer: &'a mut [u8; NUM_DIGITS],
211        alarm: &'a A,
212        segment_activation: ActivationMode,
213        digit_activation: ActivationMode,
214        refresh_rate: usize,
215    ) -> Self {
216        // Check if the buffer has enough space to hold patterns for all digits
217        if (buffer.len() * 8) < segments.len() * digits.len() {
218            panic!("Digits Driver: provided buffer is too small");
219        }
220
221        Self {
222            segments,
223            digits,
224            buffer: TakeCell::new(buffer),
225            alarm,
226            segment_activation,
227            digit_activation,
228            current_digit: Cell::new(0),
229            timing: (1000 / (refresh_rate * digits.len())) as u8,
230        }
231    }
232
233    /// Initialize the digit and segment pins.
234    /// Does not override pins if they have already been initialized for another driver.
235    pub fn init(&self) {
236        for segment in self.segments {
237            segment.make_output();
238            self.segment_clear(segment);
239        }
240
241        for digit in self.digits {
242            digit.make_output();
243            self.digit_clear(digit);
244        }
245
246        self.next_digit();
247    }
248
249    /// Returns the number of digits on the display.
250    pub fn digits_len(&self) -> usize {
251        self.digits.len()
252    }
253
254    /// Represents each digit with its corresponding pattern.
255    fn next_digit(&self) {
256        self.digit_clear(self.digits[self.current_digit.get()]);
257        self.current_digit
258            .set((self.current_digit.get() + 1) % self.digits.len());
259        self.buffer.map(|bits| {
260            for segment in 0..self.segments.len() {
261                let location = self.current_digit.get() * self.segments.len() + segment;
262                if (bits[location / 8] >> (location % 8)) & 0x1 == 1 {
263                    self.segment_set(self.segments[segment]);
264                } else {
265                    self.segment_clear(self.segments[segment]);
266                }
267            }
268        });
269        self.digit_set(self.digits[self.current_digit.get()]);
270        let interval = self.alarm.ticks_from_ms(self.timing as u32);
271        self.alarm.set_alarm(self.alarm.now(), interval);
272    }
273
274    fn segment_set(&self, p: &P) {
275        match self.segment_activation {
276            ActivationMode::ActiveHigh => p.set(),
277            ActivationMode::ActiveLow => p.clear(),
278        }
279    }
280
281    fn segment_clear(&self, p: &P) {
282        match self.segment_activation {
283            ActivationMode::ActiveHigh => p.clear(),
284            ActivationMode::ActiveLow => p.set(),
285        }
286    }
287
288    fn digit_set(&self, p: &P) {
289        match self.digit_activation {
290            ActivationMode::ActiveHigh => p.set(),
291            ActivationMode::ActiveLow => p.clear(),
292        }
293    }
294
295    fn digit_clear(&self, p: &P) {
296        match self.digit_activation {
297            ActivationMode::ActiveHigh => p.clear(),
298            ActivationMode::ActiveLow => p.set(),
299        }
300    }
301
302    /// Sets the pattern for the digit on the requested position.
303    fn print_digit(&self, position: usize, digit: usize) -> Result<(), ErrorCode> {
304        if position <= self.digits.len() {
305            self.buffer.map(|bits| bits[position - 1] = DIGITS[digit]);
306            Ok(())
307        } else {
308            Err(ErrorCode::INVAL)
309        }
310    }
311
312    /// Clears all digits currently being displayed.
313    fn clear_digits(&self) -> Result<(), ErrorCode> {
314        self.buffer.map(|bits| {
315            for index in 0..self.digits.len() {
316                bits[index] = 0;
317            }
318        });
319        Ok(())
320    }
321
322    /// Prints a dot at the requested digit position.
323    fn print_dot(&self, position: usize) -> Result<(), ErrorCode> {
324        if position <= self.digits.len() {
325            self.buffer.map(|bits| {
326                // set the first bit of the digit on this position
327                bits[position - 1] |= 1 << (self.segments.len() - 1);
328            });
329            Ok(())
330        } else {
331            Err(ErrorCode::INVAL)
332        }
333    }
334
335    /// Prints a custom pattern at a requested position.
336    fn print(&self, position: usize, pattern: u8) -> Result<(), ErrorCode> {
337        if position <= self.digits.len() {
338            self.buffer.map(|bits| {
339                bits[position - 1] = pattern;
340            });
341            Ok(())
342        } else {
343            Err(ErrorCode::INVAL)
344        }
345    }
346}
347
348impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> AlarmClient
349    for SevenSegmentDriver<'a, P, A, NUM_DIGITS>
350{
351    fn alarm(&self) {
352        self.next_digit();
353    }
354}
355
356impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> SyscallDriver
357    for SevenSegmentDriver<'a, P, A, NUM_DIGITS>
358{
359    /// Control the digit display.
360    ///
361    /// ### `command_num`
362    ///
363    /// - `0`: Driver existence check.
364    /// - `1`: Prints one digit at the requested position. Returns `INVAL` if
365    ///   the position is not valid.
366    /// - `2`: Clears all digits currently being displayed.
367    /// - `3`: Print a dot at the requested digit position. Returns `INVAL` if
368    ///   the position is not valid.
369    /// - `4`: Print a custom pattern for a certain digit. Returns `INVAL` if
370    ///   the position is not valid.
371    /// - `5`: Returns the number of digits on the display. This will always be
372    ///   0 or greater, and therefore also allows for checking for this driver.
373    fn command(
374        &self,
375        command_num: usize,
376        data1: usize,
377        data2: usize,
378        _: ProcessId,
379    ) -> CommandReturn {
380        match command_num {
381            // Check existence
382            0 => CommandReturn::success(),
383
384            // Print one digit
385            1 => CommandReturn::from(self.print_digit(data1, data2)),
386
387            // Clear all digits
388            2 => CommandReturn::from(self.clear_digits()),
389
390            // Print dot
391            3 => CommandReturn::from(self.print_dot(data1)),
392
393            // Print a custom pattern
394            4 => CommandReturn::from(self.print(data1, data2 as u8)),
395
396            // Return number of digits
397            5 => CommandReturn::success_u32(self.digits.len() as u32),
398
399            // default
400            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
401        }
402    }
403
404    fn allocate_grant(&self, _processid: ProcessId) -> Result<(), kernel::process::Error> {
405        Ok(())
406    }
407}