Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers/digit7seg: add 4-digit 7-segment display driver #20981

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions drivers/digit7seg/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base
2 changes: 2 additions & 0 deletions drivers/digit7seg/Makefile.dep
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_timer_periodic
2 changes: 2 additions & 0 deletions drivers/digit7seg/Makefile.include
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
USEMODULE_INCLUDES_digit7seg := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_digit7seg)
158 changes: 158 additions & 0 deletions drivers/digit7seg/digit7seg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright (C) 2024 Orange
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @ingroup drivers_digit7seg
* @file
* @brief Device driver for less than 5 digits of 7 segments without IC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @brief Device driver for less than 5 digits of 7 segments without IC
* @brief Device driver for up to 4 digits of 7 segments without IC

do we really want such restriction?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, most 7-digit segments are composed of 4 digits, which is easier to manage.

* @author Pierre Le Meur <pierre1.lemeur@orange.com>
*/

#include "digit7seg.h"

#define ENABLE_DEBUG 0
#include "debug.h"

#define PIN_A (dev->params.data_a)
#define PIN_B (dev->params.data_b)
#define PIN_C (dev->params.data_c)
#define PIN_D (dev->params.data_d)
#define PIN_E (dev->params.data_e)
#define PIN_F (dev->params.data_f)
#define PIN_G (dev->params.data_g)
#define PIN_DP (dev->params.data_dp)
#define PIN_DIG1 (dev->params.dig1)
#define PIN_DIG2 (dev->params.dig2)
#define PIN_DIG3 (dev->params.dig3)
#define PIN_DIG4 (dev->params.dig4)

/** The 7 segments + decimal point + the number of digits */
#define NB_PIN (8+dev->params.digits)
#define BYTE_BITS (0x08)

static void _set_pin_value(digit7seg_t *dev)
{
const gpio_t pins[] =
{
PIN_A, PIN_B, PIN_C, PIN_D, PIN_E, PIN_F, PIN_G, PIN_DP
};

uint8_t mask = 0xFF;
uint8_t current_value = (uint8_t)(dev->value >> (dev->current_digit * 8) & mask);

for (int i = 0; i < BYTE_BITS; i++) {
if (current_value & (1 << i)) {
gpio_set(pins[i]);
DEBUG("PIN SET\n");
}
else {
gpio_clear(pins[i]);
DEBUG("PIN CLEAR\n");
}
}
}

static void _shift_display(void *arg, int chan)
{
(void)chan;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why having the variable in this private function if it is unused?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timer_init want a timer_cb_t that is : typedef void (*timer_cb_t)(void *arg, int channel);

digit7seg_t *dev = (digit7seg_t*)arg;

const gpio_t digit_pins[] =
{
PIN_DIG1, PIN_DIG2, PIN_DIG3, PIN_DIG4
};

gpio_clear(digit_pins[dev->current_digit]);
dev->current_digit += 1;
dev->current_digit = dev->current_digit % dev->params.digits;
gpio_set(digit_pins[dev->current_digit]);

DEBUG("[INFO] On display %d\n", dev->current_digit);

_set_pin_value(dev);
}

int digit7seg_init(digit7seg_t *dev, const digit7seg_params_t *params)
{
dev->params = *params;
dev->current_digit = 0;
dev->value = 0;

if (dev->params.digits <= 0 || dev->params.digits > DIGIT7SEG_MAX_DIGITS) {
DEBUG("[Error] Invalid number of digit.\n");
return -DIGIT7SEG_ERR_DIGITS;
}

const gpio_t pins[] =
{
PIN_A, PIN_B, PIN_C, PIN_D, PIN_E, PIN_F, PIN_G, PIN_DP,
PIN_DIG1, PIN_DIG2, PIN_DIG3, PIN_DIG4
};

const int pin_errs[] =
{
DIGIT7SEG_ERR_A_GPIO, DIGIT7SEG_ERR_B_GPIO, DIGIT7SEG_ERR_C_GPIO, DIGIT7SEG_ERR_D_GPIO,
DIGIT7SEG_ERR_E_GPIO, DIGIT7SEG_ERR_F_GPIO, DIGIT7SEG_ERR_G_GPIO, DIGIT7SEG_ERR_DP_GPIO,
DIGIT7SEG_ERR_DIG1_GPIO, DIGIT7SEG_ERR_DIG2_GPIO, DIGIT7SEG_ERR_DIG3_GPIO,
DIGIT7SEG_ERR_DIG4_GPIO
};

for (int i = 0; i < NB_PIN; i++) {
if (!gpio_is_valid(pins[i])) {
plmorange marked this conversation as resolved.
Show resolved Hide resolved
DEBUG("[Error] GPIO isn't valid.\n");
return -pin_errs[i];
}

if (gpio_init(pins[i], GPIO_OUT) < 0) {
DEBUG("[Error] Initializing gpio error.\n");
plmorange marked this conversation as resolved.
Show resolved Hide resolved
return -pin_errs[i];
}

gpio_clear(pins[i]);
}

return DIGIT7SEG_OK;
}

void digit7seg_set_all_value(digit7seg_t *dev, uint32_t value)
{
dev->value = value;
}

int digit7seg_set_value(digit7seg_t *dev, int index, uint8_t value)
{
if (index < 0 || index >= dev->params.digits) {
return -1;
}

uint32_t temp_value = value << (index * BYTE_BITS);
uint32_t up_value = dev->value >> ((index + 1) * BYTE_BITS);
up_value <<= ((index + 1) * BYTE_BITS);
uint32_t down_value = ((0b00000001 << (index * BYTE_BITS)) - 1) & dev->value;
Comment on lines +122 to +125
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it's worth adding some explaining comments to what this code does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right


dev->value = up_value | temp_value | down_value;

return 0;
}

int digit7seg_poweron(digit7seg_t *dev)
{

if (timer_init(dev->params.timer, DIGIT7SEG_TIMER_HZ, _shift_display, dev) != 0) {
DEBUG("[Error] Not possible to init timer.\n");
return -1;
}

timer_set_periodic(dev->params.timer, 0, DIGIT7SEG_DELAY, TIM_FLAG_RESET_ON_MATCH);
return 0;
}

void digit7seg_poweroff(digit7seg_t *dev)
{
timer_stop(dev->params.timer);
}
185 changes: 185 additions & 0 deletions drivers/digit7seg/doc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright (C) 2024 Orange
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**

@defgroup drivers_digit7seg Generic 7 segments driver
@ingroup drivers_display
@brief Device driver for less than 5 digits of 7 segments without IC

## About

This driver was developed to works with [5461AS](http://www.topliteusa.com/uploadfile/2014/0825/A-5461AS.pdf)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This driver was developed to works with [5461AS](http://www.topliteusa.com/uploadfile/2014/0825/A-5461AS.pdf)
This driver was developed to work with [5461AS](http://www.topliteusa.com/uploadfile/2014/0825/A-5461AS.pdf)

in a way that all 4 digits (and less) with 7 segments without integrated controller
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
in a way that all 4 digits (and less) with 7 segments without integrated controller
in a way that up to 4 digits with 7 segments without integrated controller

can be controlled into [RIOT-OS](https://github.com/RIOT-OS/RIOT).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
can be controlled into [RIOT-OS](https://github.com/RIOT-OS/RIOT).
can be controlled from RIOT-OS.

this will become part of doc.riot-os.org, so no need to link to the RIOT github page I'd say.


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth mentioning one free HW timer as a prerequisites for using this driver here.

### 7 segments

Each digit contains 7 digit (plus decimal point), if the segment is set high and his
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Each digit contains 7 digit (plus decimal point), if the segment is set high and his
Each digit contains 7 segments (plus decimal point), if the segment is set high and his

digit is set high it will be lit.
Each value is cut into 8 bits (a, b, c, d, e, f, g, dp), if the bit is 1 current is sent otherwise
not.

To display 0 we will pass the number
```
dp g f e d c b a
0 0 1 1 1 1 1 1
```
```
--a--
| |
f b
| |
--g--
| |
e c
| |
--d-- .dp
```

### 4 digits of 7 segments

To set up a 4 digits of 7 segments display, we need transistors to allow current to pass
only when the corresponding digit pin digX is high.

All the digits share the same value pins. To display different values on each digit, we shift the value
and turn on each digit at a rate that is imperceptible to the human eye.

```
.----.----.----.----.
| | | | |-- a
| | | | |-- b
| | | | |-- c
| | | | |-- d
| | | | |-- e
| | | | |-- f
| | | | |-- g
| | | | |-- dp
'----'----'----'----'
| | | |
T-------------------- dig1
| | | |
| T--------------- dig2
| | | |
| | T---------- dig3
| | | |
| | | T----- dig4
| | | |
└──--└──--└──--└──--- GND
```

@note T represent a NPN transistor

## Error Handling

Most of driver functions return 0 on success or one of a negative error code defined by
#digit7seg_error_codes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#digit7seg_error_codes.
@ref digit7seg_error_codes.

I think, and same below.


## Usage

To use the digit7seg driver, the configuration must be set by including `digit7seg_params.h`
or defining a `digit7seg_params_t` struct.

The structure of a configuration is defined in #digit7seg_params_t and consists of the following
parameters:

Parameter | Symbol in ```digit7seg_params.h``` | Default
:------------------------------|:-----------------------------------|:------------------
Data pin for a segment | DIGIT7SEG_PARAM_A_PIN | GPIO_PIN(1, 11)
Data pin for b segment | DIGIT7SEG_PARAM_B_PIN | GPIO_PIN(1, 12)
Data pin for c segment | DIGIT7SEG_PARAM_C_PIN | GPIO_PIN(1, 15)
Data pin for d segment | DIGIT7SEG_PARAM_D_PIN | GPIO_PIN(1, 13)
Data pin for e segment | DIGIT7SEG_PARAM_E_PIN | GPIO_PIN(1, 14)
Data pin for f segment | DIGIT7SEG_PARAM_F_PIN | GPIO_PIN(0, 23)
Data pin for g segment | DIGIT7SEG_PARAM_G_PIN | GPIO_PIN(0, 21)
Data pin for dp segment | DIGIT7SEG_PARAM_DP_PIN | GPIO_PIN(0, 27)
Pin to on digit 1 | DIGIT7SEG_PARAM_DIG1_PIN | GPIO_PIN(1, 2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Pin to on digit 1 | DIGIT7SEG_PARAM_DIG1_PIN | GPIO_PIN(1, 2)
Pin to digit 1 | DIGIT7SEG_PARAM_DIG1_PIN | GPIO_PIN(1, 2)

same below

Pin to on digit 2 | DIGIT7SEG_PARAM_DIG2_PIN | GPIO_PIN(1, 1)
Pin to on digit 3 | DIGIT7SEG_PARAM_DIG3_PIN | GPIO_PIN(1, 8)
Pin to on digit 4 | DIGIT7SEG_PARAM_DIG4_PIN | GPIO_PIN(0, 13)
Timer for periodic interrupt | DIGIT7SEG_PARAM_TIMER | TIMER_DEV(2)
Number of digits on the periph | DIGIT7SEG_PARAM_DIGITS | 4

The default configuration can be overwritten by the application.

Example:
```
#define DIGIT7SEG_PARAM_TIMER TIMER_DEV(1)
#define DIGIT7SEG_PARAM_DIGITS (2)
...
#include "digit7seg.h"
#include "digit7seg_param.h"
```

Another way to override params is to override DIGIT7SEG_PARAMS that will
be stocked into digit7seg_params.

@note At least all the data_* pin must be defined, depending of the number of digits set, dig4, dig3, dig2 can be set at GPIO_UNDEF.

Example:
```
#define DIGIT7SEG_PARAMS { .data_a = GPIO(1, 14), \
.data_b = GPIO(1, 17), \
.data_c = GPIO(1, 15), \
.data_d = GPIO(1, 13), \
.data_e = GPIO(1, 12), \
.data_f = GPIO(0, 23), \
.data_g = GPIO(0, 21), \
.data_dp = GPIO(1, 11), \
.dig1 = GPIO(0, 13), \
.dig2 = GPIO(1, 8), \
.dig3 = GPIO_UNDEF, \
.dig4 = GPIO_UNDEF, \
.timer = TIMER_DEV(2), \
.digits = 2 }
...
#include "digit7seg.h"
#include "digit7seg_param.h"
```

The ::digit7seg_init function initializes a #digit7seg_t and checks if every parameter is correctly set.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe you know more than me and this is actually some special doxygen-markdown syntax?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a special dioxygen ref


Example:
```
digit7seg_t dev;

if (digit7seg_init(&dev, &digit7seg_params[0]) != DIGIT7SEG_OK) {
... /* error handling */
}
```

Once the device is initialized and configured :
- ::digit7seg_set_all_value function can be used to set the value for all the digits.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- ::digit7seg_set_all_value function can be used to set the value for all the digits.
- ::digit7seg_set_all_value function can be used to set the same value to all digits.

- ::digit7seg_poweron function starts a periodic timer that cycles through each digit to give the impression that they are all lit simultaneously.
- ::digit7seg_poweroff function stops this timer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also mention the function to set the value to a single digit.


Example:
```
/* R I O T */
/* 11101110 00001100 11111100 00001110 */
uint32_t binary_riot = 0b11101110000011001111110000001110;
digit7seg_set_all_value(&dev, binary_riot);

if (digit7seg_poweron(&dev) == 0) {
puts("Launched...");
}
else {
puts("Error");
}

...

if (digit7seg_poweroff(&dev) == 0) {
puts("...Stopped");
}
else {
puts("Error");
}

```
*/
Loading
Loading