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::hil::gpio::{ActivationMode, Pin};
154use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks};
155use kernel::syscall::{CommandReturn, SyscallDriver};
156use kernel::utilities::cells::TakeCell;
157use kernel::ErrorCode;
158use kernel::ProcessId;
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.
187
188pub struct SevenSegmentDriver<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> {
189    /// An array of 8 segment pins (7 for digit segments and one dot segment)
190    segments: &'a [&'a P; 8],
191    /// An array of `NUM_DIGITS` digit pins, each one corresponding to one digit on the display
192    /// For each digit selected, a pattern of lit and unlit segments will be represented
193    digits: &'a [&'a P; NUM_DIGITS],
194    /// A buffer which contains the patterns displayed for each digit
195    /// Each element of the buffer array represents the pattern for one digit, and
196    /// is a sequence of bits that have the value 1 for a lit segment and the value 0 for
197    /// an unlit segment.
198    buffer: TakeCell<'a, [u8; NUM_DIGITS]>,
199    alarm: &'a A,
200    current_digit: Cell<usize>,
201    /// How fast the driver should switch between digits (ms)
202    timing: u8,
203    segment_activation: ActivationMode,
204    digit_activation: ActivationMode,
205}
206
207impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> SevenSegmentDriver<'a, P, A, NUM_DIGITS> {
208    pub fn new(
209        segments: &'a [&'a P; 8],
210        digits: &'a [&'a P; NUM_DIGITS],
211        buffer: &'a mut [u8; NUM_DIGITS],
212        alarm: &'a A,
213        segment_activation: ActivationMode,
214        digit_activation: ActivationMode,
215        refresh_rate: usize,
216    ) -> Self {
217        // Check if the buffer has enough space to hold patterns for all digits
218        if (buffer.len() * 8) < segments.len() * digits.len() {
219            panic!("Digits Driver: provided buffer is too small");
220        }
221
222        Self {
223            segments,
224            digits,
225            buffer: TakeCell::new(buffer),
226            alarm,
227            segment_activation,
228            digit_activation,
229            current_digit: Cell::new(0),
230            timing: (1000 / (refresh_rate * digits.len())) as u8,
231        }
232    }
233
234    /// Initialize the digit and segment pins.
235    /// Does not override pins if they have already been initialized for another driver.
236    pub fn init(&self) {
237        for segment in self.segments {
238            segment.make_output();
239            self.segment_clear(segment);
240        }
241
242        for digit in self.digits {
243            digit.make_output();
244            self.digit_clear(digit);
245        }
246
247        self.next_digit();
248    }
249
250    /// Returns the number of digits on the display.
251    pub fn digits_len(&self) -> usize {
252        self.digits.len()
253    }
254
255    /// Represents each digit with its corresponding pattern.
256    fn next_digit(&self) {
257        self.digit_clear(self.digits[self.current_digit.get()]);
258        self.current_digit
259            .set((self.current_digit.get() + 1) % self.digits.len());
260        self.buffer.map(|bits| {
261            for segment in 0..self.segments.len() {
262                let location = self.current_digit.get() * self.segments.len() + segment;
263                if (bits[location / 8] >> (location % 8)) & 0x1 == 1 {
264                    self.segment_set(self.segments[segment]);
265                } else {
266                    self.segment_clear(self.segments[segment]);
267                }
268            }
269        });
270        self.digit_set(self.digits[self.current_digit.get()]);
271        let interval = self.alarm.ticks_from_ms(self.timing as u32);
272        self.alarm.set_alarm(self.alarm.now(), interval);
273    }
274
275    fn segment_set(&self, p: &P) {
276        match self.segment_activation {
277            ActivationMode::ActiveHigh => p.set(),
278            ActivationMode::ActiveLow => p.clear(),
279        }
280    }
281
282    fn segment_clear(&self, p: &P) {
283        match self.segment_activation {
284            ActivationMode::ActiveHigh => p.clear(),
285            ActivationMode::ActiveLow => p.set(),
286        }
287    }
288
289    fn digit_set(&self, p: &P) {
290        match self.digit_activation {
291            ActivationMode::ActiveHigh => p.set(),
292            ActivationMode::ActiveLow => p.clear(),
293        }
294    }
295
296    fn digit_clear(&self, p: &P) {
297        match self.digit_activation {
298            ActivationMode::ActiveHigh => p.clear(),
299            ActivationMode::ActiveLow => p.set(),
300        }
301    }
302
303    /// Sets the pattern for the digit on the requested position.
304    fn print_digit(&self, position: usize, digit: usize) -> Result<(), ErrorCode> {
305        if position <= self.digits.len() {
306            self.buffer.map(|bits| bits[position - 1] = DIGITS[digit]);
307            Ok(())
308        } else {
309            Err(ErrorCode::INVAL)
310        }
311    }
312
313    /// Clears all digits currently being displayed.
314    fn clear_digits(&self) -> Result<(), ErrorCode> {
315        self.buffer.map(|bits| {
316            for index in 0..self.digits.len() {
317                bits[index] = 0;
318            }
319        });
320        Ok(())
321    }
322
323    /// Prints a dot at the requested digit position.
324    fn print_dot(&self, position: usize) -> Result<(), ErrorCode> {
325        if position <= self.digits.len() {
326            self.buffer.map(|bits| {
327                // set the first bit of the digit on this position
328                bits[position - 1] |= 1 << (self.segments.len() - 1);
329            });
330            Ok(())
331        } else {
332            Err(ErrorCode::INVAL)
333        }
334    }
335
336    /// Prints a custom pattern at a requested position.
337    fn print(&self, position: usize, pattern: u8) -> Result<(), ErrorCode> {
338        if position <= self.digits.len() {
339            self.buffer.map(|bits| {
340                bits[position - 1] = pattern;
341            });
342            Ok(())
343        } else {
344            Err(ErrorCode::INVAL)
345        }
346    }
347}
348
349impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> AlarmClient
350    for SevenSegmentDriver<'a, P, A, NUM_DIGITS>
351{
352    fn alarm(&self) {
353        self.next_digit();
354    }
355}
356
357impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> SyscallDriver
358    for SevenSegmentDriver<'a, P, A, NUM_DIGITS>
359{
360    /// Control the digit display.
361    ///
362    /// ### `command_num`
363    ///
364    /// - `0`: Driver existence check.
365    /// - `1`: Prints one digit at the requested position. Returns `INVAL` if
366    ///   the position is not valid.
367    /// - `2`: Clears all digits currently being displayed.
368    /// - `3`: Print a dot at the requested digit position. Returns `INVAL` if
369    ///   the position is not valid.
370    /// - `4`: Print a custom pattern for a certain digit. Returns `INVAL` if
371    ///   the position is not valid.
372    /// - `5`: Returns the number of digits on the display. This will always be
373    ///   0 or greater, and therefore also allows for checking for this driver.
374    fn command(
375        &self,
376        command_num: usize,
377        data1: usize,
378        data2: usize,
379        _: ProcessId,
380    ) -> CommandReturn {
381        match command_num {
382            // Check existence
383            0 => CommandReturn::success(),
384
385            // Print one digit
386            1 => CommandReturn::from(self.print_digit(data1, data2)),
387
388            // Clear all digits
389            2 => CommandReturn::from(self.clear_digits()),
390
391            // Print dot
392            3 => CommandReturn::from(self.print_dot(data1)),
393
394            // Print a custom pattern
395            4 => CommandReturn::from(self.print(data1, data2 as u8)),
396
397            // Return number of digits
398            5 => CommandReturn::success_u32(self.digits.len() as u32),
399
400            // default
401            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
402        }
403    }
404
405    fn allocate_grant(&self, _processid: ProcessId) -> Result<(), kernel::process::Error> {
406        Ok(())
407    }
408}