mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-17 18:43:50 +01:00
330 lines
8.1 KiB
C
330 lines
8.1 KiB
C
/*
|
|
* Copyright (C) 2024 Nico Behrens <nifrabe@outlook.de>
|
|
*
|
|
* 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 <nifrabe@outlook.de>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include "tm1637.h"
|
|
|
|
#include "periph/gpio.h"
|
|
#include "ztimer.h"
|
|
#include "log.h"
|
|
|
|
/**
|
|
* @brief Amount of digits
|
|
*/
|
|
#define DIGIT_COUNT 4
|
|
|
|
/**
|
|
* @brief Signals data transmission
|
|
*/
|
|
#define COMMAND_DATA 0x40
|
|
|
|
/**
|
|
* @brief Sets the brightness and display state to on/off
|
|
*/
|
|
#define COMMAND_DISPLAY_AND_CONTROL 0x80
|
|
|
|
/**
|
|
* @brief Sets the address where data is written
|
|
*/
|
|
#define COMMAND_ADDRESS 0xC0
|
|
|
|
/**
|
|
* @brief Bit mask for turning the display on/off
|
|
*
|
|
* @note This bit is part of the COMMAND_DISPLAY_AND_CONTROL
|
|
*/
|
|
#define BIT_MASK_ON 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 BIT_MASK_DOT 0x80
|
|
|
|
/**
|
|
* @brief Delay between bits in microseconds
|
|
*/
|
|
#define BIT_TIME_US 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
|
|
*/
|
|
inline static void _tm1637_delay(void)
|
|
{
|
|
ztimer_sleep(ZTIMER_USEC, BIT_TIME_US);
|
|
}
|
|
|
|
/**
|
|
* @brief Starts the transmission to the display
|
|
*
|
|
* @param[in] dev device descriptor of the display
|
|
*/
|
|
static void _tm1637_start(const tm1637_t *dev)
|
|
{
|
|
gpio_write(dev->params->dio, false);
|
|
_tm1637_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 _tm1637_stop(const tm1637_t *dev)
|
|
{
|
|
gpio_write(dev->params->dio, false);
|
|
_tm1637_delay();
|
|
gpio_write(dev->params->clk, true);
|
|
_tm1637_delay();
|
|
gpio_write(dev->params->dio, true);
|
|
_tm1637_delay();
|
|
}
|
|
|
|
/**
|
|
* @brief Transmits a single byte to the display
|
|
*
|
|
* @param[in] dev device descriptor of the display
|
|
* @param[in] byte byte to transmit
|
|
*
|
|
* @retval 0 if the transmission was successful
|
|
* @retval -1 if the transmission failed
|
|
*/
|
|
static int _tm1637_transmit_byte(const tm1637_t *dev, uint8_t byte)
|
|
{
|
|
/* transmit each bit */
|
|
for (int i = 0; i < 8; ++i) {
|
|
bool value = (byte >> i) & 0x01;
|
|
gpio_write(dev->params->clk, false);
|
|
_tm1637_delay();
|
|
gpio_write(dev->params->dio, value);
|
|
_tm1637_delay();
|
|
gpio_write(dev->params->clk, true);
|
|
_tm1637_delay();
|
|
}
|
|
|
|
gpio_write(dev->params->clk, false);
|
|
gpio_write(dev->params->dio, true);
|
|
|
|
/* set the DIO pin to input to later receive the ACK */
|
|
gpio_init(dev->params->dio, GPIO_IN_PU);
|
|
_tm1637_delay();
|
|
|
|
gpio_write(dev->params->clk, true);
|
|
|
|
/* the transmission is successful if the GPIO reads LOW */
|
|
bool nack = gpio_read(dev->params->dio);
|
|
_tm1637_delay();
|
|
|
|
/* set the DIO pin back to output */
|
|
gpio_init(dev->params->dio, GPIO_OUT);
|
|
gpio_write(dev->params->dio, false);
|
|
gpio_write(dev->params->clk, false);
|
|
_tm1637_delay();
|
|
|
|
return nack ? -1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
*
|
|
* @retval 0 if the transmission was successful
|
|
* @retval -1 if the transmission failed
|
|
*/
|
|
static int _tm1637_transmit_segments(const tm1637_t *dev,
|
|
const uint8_t segments[DIGIT_COUNT],
|
|
tm1637_brightness_t brightness)
|
|
{
|
|
/* transmit the data command first */
|
|
_tm1637_start(dev);
|
|
int res = _tm1637_transmit_byte(dev, COMMAND_DATA);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
|
|
_tm1637_stop(dev);
|
|
|
|
/**
|
|
* set the address using the auto increment mode for addresses and
|
|
* start at zero
|
|
*/
|
|
_tm1637_start(dev);
|
|
res = _tm1637_transmit_byte(dev, COMMAND_ADDRESS);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* transmit each byte indiviudally */
|
|
for (int i = 0; i < DIGIT_COUNT; ++i) {
|
|
res = _tm1637_transmit_byte(dev, segments[i]);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
_tm1637_stop(dev);
|
|
|
|
/* transmit the display state and the brightness */
|
|
_tm1637_start(dev);
|
|
res = _tm1637_transmit_byte(dev, COMMAND_DISPLAY_AND_CONTROL |
|
|
brightness | BIT_MASK_ON);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
_tm1637_stop(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Modifies the segments array to enable the colon
|
|
*
|
|
* @param[in,out] segments segments to enable the colon on
|
|
*/
|
|
inline static void _enable_colon(uint8_t *segments)
|
|
{
|
|
/* the digit to the left of the middle uses the colon */
|
|
segments[DIGIT_COUNT / 2 - 1] |= BIT_MASK_DOT;
|
|
}
|
|
|
|
int tm1637_init(tm1637_t *dev, const tm1637_params_t *params)
|
|
{
|
|
assert(params != NULL);
|
|
assert(dev != NULL);
|
|
assert(params->clk != GPIO_UNDEF);
|
|
assert(params->dio != GPIO_UNDEF);
|
|
|
|
/* 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);
|
|
|
|
/**
|
|
* This is necessary as the display needs to warm-up to the clock speed.
|
|
* If the following lines are omitted, the display will generate a NACK after the first
|
|
* byte transmission.
|
|
*/
|
|
_tm1637_start(dev);
|
|
_tm1637_stop(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tm1637_clear(const tm1637_t *dev)
|
|
{
|
|
uint8_t segments[DIGIT_COUNT] = { 0 };
|
|
return _tm1637_transmit_segments(dev, segments, TM1637_PW_1_16);
|
|
}
|
|
|
|
int 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 };
|
|
|
|
if (number == 0) {
|
|
segments[DIGIT_COUNT - 1] = segments_array[0];
|
|
}
|
|
else if (number < 0) {
|
|
/* for a negative number invert the signedness */
|
|
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) {
|
|
/**
|
|
* without leading zeros, the minus sign is to the left of the most
|
|
* significant digit
|
|
*/
|
|
segments[DIGIT_COUNT - 1 - i] = minus_sign;
|
|
break;
|
|
}
|
|
else {
|
|
/* with leading zeros, the minus sign is always at the leftmost position */
|
|
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);
|
|
}
|
|
|
|
return _tm1637_transmit_segments(dev, segments, brightness);
|
|
}
|