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}