ESPHome  2024.4.1
max7219digit.cpp
Go to the documentation of this file.
1 #include "max7219digit.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/helpers.h"
4 #include "esphome/core/hal.h"
5 #include "max7219font.h"
6 
7 namespace esphome {
8 namespace max7219digit {
9 
10 static const char *const TAG = "max7219DIGIT";
11 
12 static const uint8_t MAX7219_REGISTER_NOOP = 0x00;
13 static const uint8_t MAX7219_REGISTER_DECODE_MODE = 0x09;
14 static const uint8_t MAX7219_REGISTER_INTENSITY = 0x0A;
15 static const uint8_t MAX7219_REGISTER_SCAN_LIMIT = 0x0B;
16 static const uint8_t MAX7219_REGISTER_SHUTDOWN = 0x0C;
17 static const uint8_t MAX7219_REGISTER_DISPLAY_TEST = 0x0F;
18 constexpr uint8_t MAX7219_NO_SHUTDOWN = 0x00;
19 constexpr uint8_t MAX7219_SHUTDOWN = 0x01;
20 constexpr uint8_t MAX7219_NO_DISPLAY_TEST = 0x00;
21 constexpr uint8_t MAX7219_DISPLAY_TEST = 0x01;
22 
24 
26  ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS...");
27  this->spi_setup();
28  this->stepsleft_ = 0;
29  for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
30  std::vector<uint8_t> vec(1);
31  this->max_displaybuffer_.push_back(vec);
32  // Initialize buffer with 0 for display so all non written pixels are blank
33  this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0);
34  }
35  // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
36  this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7);
37  // let's use our own ASCII -> led pattern encoding
38  this->send_to_all_(MAX7219_REGISTER_DECODE_MODE, 0);
39  // No display test with all the pixels on
40  this->send_to_all_(MAX7219_REGISTER_DISPLAY_TEST, MAX7219_NO_DISPLAY_TEST);
41  // SET Intsity of display
42  this->send_to_all_(MAX7219_REGISTER_INTENSITY, this->intensity_);
43  // this->send_to_all_(MAX7219_REGISTER_INTENSITY, 1);
44  this->display();
45  // power up
46  this->send_to_all_(MAX7219_REGISTER_SHUTDOWN, 1);
47 }
48 
50  ESP_LOGCONFIG(TAG, "MAX7219DIGIT:");
51  ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_);
52  ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_);
53  ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_);
54  ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_);
55  ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_);
56  ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_);
57  ESP_LOGCONFIG(TAG, " Scroll Dwell: %u", this->scroll_dwell_);
58  ESP_LOGCONFIG(TAG, " Scroll Delay: %u", this->scroll_delay_);
59  LOG_PIN(" CS Pin: ", this->cs_);
60  LOG_UPDATE_INTERVAL(this);
61 }
62 
64  uint32_t now = millis();
65 
66  // check if the buffer has shrunk past the current position since last update
67  if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) ||
68  (this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) {
69  this->stepsleft_ = 0;
70  this->display();
71  this->old_buffer_size_ = this->max_displaybuffer_[0].size();
72  }
73 
74  // Reset the counter back to 0 when full string has been displayed.
75  if (this->stepsleft_ > this->max_displaybuffer_[0].size())
76  this->stepsleft_ = 0;
77 
78  // Return if there is no need to scroll or scroll is off
79  if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= (size_t) get_width_internal())) {
80  this->display();
81  return;
82  }
83 
84  if ((this->stepsleft_ == 0) && (now - this->last_scroll_ < this->scroll_delay_)) {
85  this->display();
86  return;
87  }
88 
89  // Dwell time at end of string in case of stop at end
90  if (this->scroll_mode_ == ScrollMode::STOP) {
91  if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - (size_t) get_width_internal() + 1) {
92  if (now - this->last_scroll_ >= this->scroll_dwell_) {
93  this->stepsleft_ = 0;
94  this->last_scroll_ = now;
95  this->display();
96  }
97  return;
98  }
99  }
100 
101  // Actual call to scroll left action
102  if (now - this->last_scroll_ >= this->scroll_speed_) {
103  this->last_scroll_ = now;
104  this->scroll_left();
105  this->display();
106  }
107 }
108 
110  uint8_t pixels[8];
111  // Run this loop for every MAX CHIP (GRID OF 64 leds)
112  // Run this routine for the rows of every chip 8x row 0 top to 7 bottom
113  // Fill the pixel parameter with display data
114  // Send the data to the chip
115  for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) {
116  for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
117  for (uint8_t j = 0; j < 8; j++) {
118  bool reverse =
119  chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_;
120  if (reverse) {
121  pixels[j] =
122  this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j];
123  } else {
124  pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j];
125  }
126  }
127  if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
128  this->orientation_ = orientation_180_();
129  this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels);
130  if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
131  this->orientation_ = orientation_180_();
132  }
133  }
134 }
135 
137  switch (this->orientation_) {
138  case 0:
139  return 2;
140  case 1:
141  return 3;
142  case 2:
143  return 0;
144  case 3:
145  return 1;
146  default:
147  return 0;
148  }
149 }
150 
152  return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll
153 }
154 
156 
158  if (x + 1 > (int) this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required
159  for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
160  this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_);
161  }
162  }
163 
164  if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw
165  return;
166 
167  uint16_t pos = x; // X is starting at 0 top left
168  uint8_t subpos = y; // Y is starting at 0 top left
169 
170  if (color.is_on()) {
171  this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8);
172  } else {
173  this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8);
174  }
175 }
176 
177 void MAX7219Component::send_byte_(uint8_t a_register, uint8_t data) {
178  this->write_byte(a_register); // Write register value to MAX
179  this->write_byte(data); // Followed by actual data
180 }
181 void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
182  this->enable(); // Enable SPI
183  for (uint8_t i = 0; i < this->num_chips_; i++) // Run the loop for every MAX chip in the stack
184  this->send_byte_(a_register, data); // Send the data to the chips
185  this->disable(); // Disable SPI
186 }
188  this->update_ = true;
189  for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
190  this->max_displaybuffer_[chip_line].clear();
191  this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_);
192  }
193  if (this->writer_local_.has_value()) // insert Labda function if available
194  (*this->writer_local_)(*this);
195 }
196 
197 void MAX7219Component::invert_on_off(bool on_off) { this->invert_ = on_off; };
199 
200 void MAX7219Component::turn_on_off(bool on_off) {
201  if (on_off) {
202  this->send_to_all_(MAX7219_REGISTER_SHUTDOWN, 1);
203  } else {
204  this->send_to_all_(MAX7219_REGISTER_SHUTDOWN, 0);
205  }
206 }
207 
208 void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
209  this->set_scroll(on_off);
210  this->set_scroll_mode(mode);
211  this->set_scroll_speed(speed);
212  this->set_scroll_dwell(dwell);
213  this->set_scroll_delay(delay);
214 }
215 
217  this->set_scroll(on_off);
218  this->set_scroll_mode(mode);
219 }
220 
222  this->intensity_ = intensity;
223  this->send_to_all_(MAX7219_REGISTER_INTENSITY, this->intensity_);
224 }
225 
226 void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); }
227 
229  for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
230  if (this->update_) {
231  this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_);
232  for (uint16_t i = 0; i < this->stepsleft_; i++) {
233  this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
234  this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
235  }
236  } else {
237  this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
238  this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
239  }
240  }
241  this->update_ = false;
242  this->stepsleft_++;
243 }
244 
245 void MAX7219Component::send_char(uint8_t chip, uint8_t data) {
246  // get this character from PROGMEM
247  for (uint8_t i = 0; i < 8; i++)
248  this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
249 } // end of send_char
250 
251 // send one character (data) to position (chip)
252 
253 void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
254  for (uint8_t col = 0; col < 8; col++) { // RUN THIS LOOP 8 times until column is 7
255  this->enable(); // start sending by enabling SPI
256  for (uint8_t i = 0; i < chip; i++) { // send extra NOPs to push the pixels out to extra displays
257  this->send_byte_(MAX7219_REGISTER_NOOP,
258  MAX7219_REGISTER_NOOP); // run this loop unit the matching chip is reached
259  }
260  uint8_t b = 0; // rotate pixels 90 degrees -- set byte to 0
261  if (this->orientation_ == 0) {
262  for (uint8_t i = 0; i < 8; i++) {
263  // run this loop 8 times for all the pixels[8] received
264  if (this->flip_x_) {
265  b |= ((pixels[i] >> col) & 1) << i; // change the column bits into row bits
266  } else {
267  b |= ((pixels[i] >> col) & 1) << (7 - i); // change the column bits into row bits
268  }
269  }
270  } else if (this->orientation_ == 1) {
271  b = pixels[col];
272  } else if (this->orientation_ == 2) {
273  for (uint8_t i = 0; i < 8; i++) {
274  if (this->flip_x_) {
275  b |= ((pixels[i] >> (7 - col)) & 1) << (7 - i);
276  } else {
277  b |= ((pixels[i] >> (7 - col)) & 1) << i;
278  }
279  }
280  } else {
281  for (uint8_t i = 0; i < 8; i++) {
282  b |= ((pixels[7 - col] >> i) & 1) << (7 - i);
283  }
284  }
285  // send this byte to display at selected chip
286  if (this->invert_) {
287  this->send_byte_(col + 1, ~b);
288  } else {
289  this->send_byte_(col + 1, b);
290  }
291  for (int i = 0; i < this->num_chips_ - chip - 1; i++) // end with enough NOPs so later chips don't update
292  this->send_byte_(MAX7219_REGISTER_NOOP, MAX7219_REGISTER_NOOP);
293  this->disable(); // all done disable SPI
294  } // end of for each column
295 } // end of send64pixels
296 
297 uint8_t MAX7219Component::printdigit(const char *str) { return this->printdigit(0, str); }
298 
299 uint8_t MAX7219Component::printdigit(uint8_t start_pos, const char *s) {
300  uint8_t chip = start_pos;
301  for (; chip < this->num_chips_ && *s; chip++)
302  send_char(chip, *s++);
303  // space out rest
304  while (chip < (this->num_chips_))
305  send_char(chip++, ' ');
306  return 0;
307 } // end of sendString
308 
309 uint8_t MAX7219Component::printdigitf(uint8_t pos, const char *format, ...) {
310  va_list arg;
311  va_start(arg, format);
312  char buffer[64];
313  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
314  va_end(arg);
315  if (ret > 0)
316  return this->printdigit(pos, buffer);
317  return 0;
318 }
319 uint8_t MAX7219Component::printdigitf(const char *format, ...) {
320  va_list arg;
321  va_start(arg, format);
322  char buffer[64];
323  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
324  va_end(arg);
325  if (ret > 0)
326  return this->printdigit(buffer);
327  return 0;
328 }
329 
330 uint8_t MAX7219Component::strftimedigit(uint8_t pos, const char *format, ESPTime time) {
331  char buffer[64];
332  size_t ret = time.strftime(buffer, sizeof(buffer), format);
333  if (ret > 0)
334  return this->printdigit(pos, buffer);
335  return 0;
336 }
337 uint8_t MAX7219Component::strftimedigit(const char *format, ESPTime time) {
338  return this->strftimedigit(0, format, time);
339 }
340 
341 } // namespace max7219digit
342 } // namespace esphome
constexpr uint8_t MAX7219_SHUTDOWN
void draw_absolute_pixel_internal(int x, int y, Color color) override
constexpr uint8_t MAX7219_DISPLAY_TEST
size_t strftime(char *buffer, size_t buffer_len, const char *format)
Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument...
Definition: time.cpp:18
void send64pixels(uint8_t chip, const uint8_t pixels[8])
uint16_t x
Definition: tt21100.cpp:17
A more user-friendly version of struct tm from time.h.
Definition: time.h:17
void send_to_all_(uint8_t a_register, uint8_t data)
int speed
Definition: fan.h:35
optional< max7219_writer_t > writer_local_
Definition: max7219digit.h:120
constexpr uint8_t MAX7219_NO_DISPLAY_TEST
GPIOPin * cs_
Definition: spi.h:395
bool has_value() const
Definition: optional.h:87
void set_scroll_mode(ScrollMode mode)
Definition: max7219digit.h:64
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
uint16_t y
Definition: tt21100.cpp:18
uint8_t strftimedigit(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime
Evaluate the strftime-format and print the result at the given position.
void send_char(uint8_t chip, uint8_t data)
std::vector< std::vector< uint8_t > > max_displaybuffer_
Definition: max7219digit.h:116
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:151
float get_setup_priority() const override
const float PROCESSOR
For components that use data from sensors like displays.
Definition: component.cpp:20
uint8_t uint8_t uint8_t printdigit(uint8_t pos, const char *str)
Print str at the given position.
void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell)
uint8_t num_chips_
Intensity of the display from 0 to 15 (most)
Definition: max7219digit.h:101
uint8_t printdigitf(uint8_t pos, const char *format,...) __attribute__((format(printf
Evaluate the printf-format and print the result at the given position.
void send_byte_(uint8_t a_register, uint8_t data)
uint8_t progmem_read_byte(const uint8_t *addr)
Definition: core.cpp:55
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
bool is_on() ALWAYS_INLINE
Definition: color.h:46
constexpr uint8_t MAX7219_NO_SHUTDOWN
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26