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}