diff --git a/drivers/include/tm1637.h b/drivers/include/tm1637.h new file mode 100644 index 0000000000..7c75c9d9f6 --- /dev/null +++ b/drivers/include/tm1637.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 Nico Behrens + * + * 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_tm1637 TM1637 display + * @ingroup drivers_display + * @brief Driver for the TM1637 4-digit 7-segment display + * + * @{ + * @file + * @brief Interface definition for the TM1637 4-digit 7-segment display driver + * + * @author Nico Behrens + * + */ + +#ifndef TM1637_H +#define TM1637_H + +#include "board.h" +#include "periph/gpio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Pin configuration parameters for the tm1637 display + */ +typedef struct { + /** + * @brief GPIO for clock + */ + gpio_t clk; + + /** + * @brief GPIO for data input/output + */ + gpio_t dio; +} tm1637_params_t; + +/** + * @brief tm1637 driver descriptor + */ +typedef struct { + /** + * @brief Configuration parameters + * + */ + tm1637_params_t params; +} tm1637_t; + +/** + * @brief Brightness level enum for the display + * + * @note The brightness is defined as a fraction of + * the pulse width and must be on of the given values. + */ +typedef enum { + TM1637_PW_1_16 = 0x00, + TM1637_PW_2_16 = 0x01, + TM1637_PW_4_16 = 0x02, + TM1637_PW_10_16 = 0x03, + TM1637_PW_11_16 = 0x04, + TM1637_PW_12_16 = 0x05, + TM1637_PW_13_16 = 0x06, + TM1637_PW_14_16 = 0x07 +} tm1637_brightness_t; + +/** + * @brief Initializes the tm1637 device + * + * @param[out] dev device descriptor of the display + * @param[in] params configuration parameters + * @return 0 on success, error otherwise + */ +int tm1637_init(tm1637_t *dev, const tm1637_params_t *params); + +/** + * @brief Writes an integer to the display + * + * @note The integer can't be bigger than 9999 or smaller than + * -999 as only 4 digits can be displayed at a time. + * With the leading zeros enabled, the display is padded with zeros. + * For negative integers the leading zeros are added between the minus sign + * and the number. + * + * @param[in] dev device descriptor of the display + * @param[in] number number to write, in the range of 9999 to -999 + * @param[in] brightness brightness of the display according to @ref tm1637_brightness_t + * @param[in] colon If enabled, displays a colon in the middle + * @param[in] leading_zeros If enabled, displays leading zeros + */ +void tm1637_write_number(const tm1637_t *dev, int16_t number, tm1637_brightness_t brightness, bool colon, bool leading_zeros); + +/** + * @brief Clear the display + * + * @param[in] dev device descriptor of the display + */ +void tm1637_clear(const tm1637_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* TM1637_H */ +/** @} */ \ No newline at end of file diff --git a/drivers/tm1637/Makefile b/drivers/tm1637/Makefile new file mode 100644 index 0000000000..e659679fcd --- /dev/null +++ b/drivers/tm1637/Makefile @@ -0,0 +1,7 @@ +MODULE = tm1637 + +USEMODULE += ztimer +USEMODULE += ztimer_msec +USEMODULE += periph_gpio + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/tm1637/Makefile.dep b/drivers/tm1637/Makefile.dep new file mode 100644 index 0000000000..58a47b3653 --- /dev/null +++ b/drivers/tm1637/Makefile.dep @@ -0,0 +1,3 @@ +FEATURES_REQUIRED += periph_gpio +USEMODULE += ztimer +USEMODULE += ztimer_msec \ No newline at end of file diff --git a/drivers/tm1637/Makefile.include b/drivers/tm1637/Makefile.include new file mode 100644 index 0000000000..08cda68518 --- /dev/null +++ b/drivers/tm1637/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_tm1637 := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_tm1637) \ No newline at end of file diff --git a/drivers/tm1637/include/tm1637_params.h b/drivers/tm1637/include/tm1637_params.h new file mode 100644 index 0000000000..4ae9dfcb7e --- /dev/null +++ b/drivers/tm1637/include/tm1637_params.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 Nico Behrens + * + * 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_tm1637 + * + * @{ + * @file + * @brief Config for the TM1637 display + * + * @author Nico Behrens + */ +#ifndef TM1637_PARAMS_H +#define TM1637_PARAMS_H + +#include "board.h" +#include "periph/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef TM1637_PARAM_CLK +/** + * @brief see @ref tm1637_params_t + */ +#define TM1637_PARAM_CLK GPIO_PIN(0, 0) +#endif + +#ifndef TM1637_PARAM_DIO +/** + * @brief see @ref tm1637_params_t + */ +#define TM1637_PARAM_DIO GPIO_PIN(0, 1) +#endif + +#ifndef TM1637_PARAMS +/** + * @brief see @ref tm1637_params_t + */ +#define TM1637_PARAMS { .clk = TM1637_PARAM_CLK, \ + .dio = TM1637_PARAM_DIO } +#endif + +/** + * @brief see @ref tm1637_params_t + */ +static const tm1637_params_t tm1637_params[] = { + TM1637_PARAMS +}; + +#ifdef __cplusplus +} +#endif + +#endif /* TM1637_PARAMS_H */ +/** @} */ \ No newline at end of file diff --git a/drivers/tm1637/tm1637.c b/drivers/tm1637/tm1637.c new file mode 100644 index 0000000000..146b84db25 --- /dev/null +++ b/drivers/tm1637/tm1637.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2024 Nico Behrens + * + * 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_tm1637 + * + * @{ + * @file + * @brief Driver for the TM1637 4-digit 7-segment display + * + * @author Nico Behrens + * + * @} + */ + +#include "tm1637.h" + +#include "periph/gpio.h" +#include "ztimer.h" + +/** + * @brief Amount of digits + */ +#define DIGIT_COUNT 4 + +/** + * @brief Signals data transmission + */ +#define DATA_COMMAND 0x40 + +/** + * @brief Sets the brightness and display state to on/off + */ +#define DISPLAY_AND_CONTROL_COMMAND 0x80 + +/** + * @brief Sets the address where data is written + */ +#define ADDRESS_COMMAND 0xC0 + +/** + * @brief Bit mask for turning the display on/off + * + * @note This bit is part of the DISPLAY_AND_CONTROL_COMMAND + */ +#define ON_BIT_MASK 0x08 + +/** + * @brief Bit mask for the dot/colon to the right of a digit + * + * @note This bit is part of a digit's segment data + */ +#define DOT_BIT_MASK 0x80 + +/** + * @brief Delay between bits in milliseconds + */ +#define BIT_TIME_MS 1 + +/** + * @brief Array encoding the segments for the digits from 0 to 9 + */ +static const uint8_t segments_array[] = { + 0b00111111, // 0 + 0b00000110, // 1 + 0b01011011, // 2 + 0b01001111, // 3 + 0b01100110, // 4 + 0b01101101, // 5 + 0b01111101, // 6 + 0b00000111, // 7 + 0b01111111, // 8 + 0b01101111, // 9 +}; + +/** + * @brief Minus sign for negative numbers + */ +static const uint8_t minus_sign = 0b01000000; + +/** + * @brief Delays the transmission to the display + */ +static void delay(void) +{ + ztimer_sleep(ZTIMER_MSEC, BIT_TIME_MS); +} + +/** + * @brief Starts the transmission to the display + * + * @param[in] dev device descriptor of the display + */ +static void start(const tm1637_t *dev) +{ + gpio_write(dev->params.dio, false); + delay(); +} + +/** + * @brief Stops the transmission to the display + * + * @note A stop is needed after transmission of certain bytes according to + * the specification. + * + * @param[in] dev device descriptor of the display + */ +static void stop(const tm1637_t *dev) +{ + gpio_write(dev->params.dio, false); + delay(); + gpio_write(dev->params.clk, true); + delay(); + gpio_write(dev->params.dio, true); + delay(); +} + +/** + * @brief Transmits a single byte to the display + * + * @param[in] dev device descriptor of the display + * @param[in] byte byte to transmit + */ +static void transmit_byte(const tm1637_t *dev, uint8_t byte) +{ + for (int i = 0; i < 8; ++i) { + bool value = (byte >> i) & 0x01; + gpio_write(dev->params.clk, false); + delay(); + gpio_write(dev->params.dio, value); + delay(); + gpio_write(dev->params.clk, true); + delay(); + } + + /* we do not read the ACK as it is not necessary for normal functionality */ + gpio_write(dev->params.clk, false); + gpio_write(dev->params.dio, true); + delay(); + gpio_write(dev->params.clk, true); + delay(); + gpio_write(dev->params.clk, false); + delay(); +} + +/** + * @brief Transmits the segments array of length 4 to the display + * + * @param[in] dev device descriptor of the display + * @param[in] segments array of length 4 encoding the display's segments + */ +static void transmit_segments(const tm1637_t *dev, + const uint8_t *segments, + tm1637_brightness_t brightness) +{ + /* transmit the data command first */ + start(dev); + transmit_byte(dev, DATA_COMMAND); + stop(dev); + + /** + * set the address using the auto increment mode for addresses and + * start at zero + */ + start(dev); + transmit_byte(dev, ADDRESS_COMMAND); + + /* transmit each bit indiviudally */ + for (int i = 0; i < DIGIT_COUNT; ++i) { + transmit_byte(dev, segments[i]); + } + stop(dev); + + /* transmit the display state and the brightness */ + start(dev); + transmit_byte(dev, DISPLAY_AND_CONTROL_COMMAND | + brightness | ON_BIT_MASK); + + stop(dev); +} + +/** + * @brief Modifies the segments array to enable the colon + * + * @param[in,out] segments segments to enable the colon on + */ +static void enable_colon(uint8_t *segments) +{ + /* the second digit uses the colon */ + segments[1] |= DOT_BIT_MASK; +} + +int tm1637_init(tm1637_t *dev, const tm1637_params_t *params) +{ + assert(params != NULL); + assert(dev != NULL); + + /* set the parameters */ + dev->params = *params; + + if (gpio_init(dev->params.clk, GPIO_OUT)) { + return -1; + } + + if (gpio_init(dev->params.dio, GPIO_OUT)) { + return -1; + } + + gpio_write(dev->params.clk, false); + gpio_write(dev->params.dio, false); + + return 0; +} + +void tm1637_clear(const tm1637_t *dev) +{ + uint8_t segments[DIGIT_COUNT] = { 0, 0, 0, 0 }; + transmit_segments(dev, segments, TM1637_PW_1_16); +} + +void tm1637_write_number(const tm1637_t *dev, int16_t number, + tm1637_brightness_t brightness, bool colon, + bool leading_zeros) +{ + /* with only 4 digits available, this range can't be exceeded */ + assert(number <= 9999); + assert(number >= -999); + + uint8_t segments[DIGIT_COUNT] = { 0, 0, 0, 0 }; + + if (number == 0) { + segments[DIGIT_COUNT - 1] = segments_array[0]; + } + else if (number < 0) { + number = -number; + for (int i = 0; i < DIGIT_COUNT; ++i) { + if (number != 0) { + segments[DIGIT_COUNT - 1 - i] = segments_array[number % 10]; + number /= 10; + } + else if (!leading_zeros) { + segments[DIGIT_COUNT - 1 - i] = minus_sign; + break; + } + else { + segments[0] = minus_sign; + break; + } + } + } + else { + for (int i = 0; i < DIGIT_COUNT; ++i) { + if (number != 0) { + segments[DIGIT_COUNT - 1 - i] = segments_array[number % 10]; + number /= 10; + } + } + } + + /* fill out all segments that have not yet been filled with zeros */ + if (leading_zeros) { + for (int i = 0; i < DIGIT_COUNT; ++i) { + if (segments[i] == 0) { + segments[i] = segments_array[0]; + } + } + } + + if (colon) { + enable_colon(segments); + } + + transmit_segments(dev, segments, brightness); +} \ No newline at end of file diff --git a/tests/drivers/tm1637/Makefile b/tests/drivers/tm1637/Makefile new file mode 100644 index 0000000000..8bd5ff16a3 --- /dev/null +++ b/tests/drivers/tm1637/Makefile @@ -0,0 +1,11 @@ +BOARD ?= nrf52840dk + +include ../Makefile.drivers_common + +USEMODULE += tm1637 + +USEMODULE += ztimer +USEMODULE += ztimer_sec + + +include $(RIOTBASE)/Makefile.include diff --git a/tests/drivers/tm1637/README.md b/tests/drivers/tm1637/README.md new file mode 100644 index 0000000000..5477432f47 --- /dev/null +++ b/tests/drivers/tm1637/README.md @@ -0,0 +1,18 @@ +# Test Application for the TM1637 4 digit 7-segment display. + +## About + +This is a simple test application for the TM1637 driver. + +## Details + +This test application will initialize the TM1637 driver with the configuration +as specified in the default `tm1637_params.h` file. The 4 pins of the display need to +be connected in this way: +- The clk pin connects to GPIO 0 +- The dio pin connects to GPIO 1 +- The VCC pin connects to either 5V or 3.3V +- The GND pin connects to GND + +## Expected result +The displays switches between different configurations, all digits and all brightness levels. \ No newline at end of file diff --git a/tests/drivers/tm1637/main.c b/tests/drivers/tm1637/main.c new file mode 100644 index 0000000000..9f660f07cb --- /dev/null +++ b/tests/drivers/tm1637/main.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 Nico Behrens + * + * 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 tests + * @{ + * + * @file + * @brief Test application for TM1637 4-digit 7-segment display driver + * + * @author Nico Behrens + * + * @} + */ + +#include "tm1637.h" +#include "tm1637_params.h" + +#include "periph/gpio.h" +#include "ztimer.h" + +#include + +/** + * @brief Display a number with different settings + * + * @param[in] dev device descriptor of the display + * @param[in] number number to display + */ +static void test_number(const tm1637_t *dev, int16_t number) +{ + tm1637_write_number(dev, number, TM1637_PW_14_16, false, false); + ztimer_sleep(ZTIMER_SEC, 1); + tm1637_write_number(dev, number, TM1637_PW_14_16, false, true); + ztimer_sleep(ZTIMER_SEC, 1); + tm1637_write_number(dev, number, TM1637_PW_14_16, true, false); + ztimer_sleep(ZTIMER_SEC, 1); + tm1637_write_number(dev, number, TM1637_PW_14_16, true, true); + ztimer_sleep(ZTIMER_SEC, 1); + tm1637_clear(dev); + ztimer_sleep(ZTIMER_SEC, 1); +} + +/** + * @brief Shows all digits on the display + * + * @param[in] dev device descriptor of the display + */ +static void test_all_digits(const tm1637_t *dev) +{ + for (int i = 0; i < 10; i++) { + tm1637_write_number(dev, i, TM1637_PW_14_16, false, false); + ztimer_sleep(ZTIMER_SEC, 1); + } + tm1637_clear(dev); +} + +/** + * @brief Test all brightness levels + * + * @param[in] dev device descriptor of the display + * @param[in] number number to display + */ +static void test_brightness(const tm1637_t *dev, uint16_t number) +{ + /* the brightness goes from 0 to 7 */ + for (int i = 0; i <= 7; i++) { + tm1637_write_number(dev, number, i, false, false); + ztimer_sleep(ZTIMER_SEC, 1); + } + tm1637_clear(dev); + ztimer_sleep(ZTIMER_SEC, 1); +} + +int main(void) +{ + puts("Starting TM1637 driver test application"); + + tm1637_t dev; + tm1637_init(&dev, &tm1637_params[0]); + + test_number(&dev, 42); + test_all_digits(&dev); + test_brightness(&dev, 1111); + + puts("Ending TM1637 driver test application"); + + return 0; +} \ No newline at end of file