FluxSand 1.0
FluxSand - Interactive Digital Hourglass
Loading...
Searching...
No Matches
max7219.hpp
1#pragma once
2
3#include <fcntl.h>
4#include <linux/spi/spidev.h>
5#include <sys/ioctl.h>
6#include <unistd.h>
7
8#include <array>
9#include <chrono>
10#include <cstdint>
11#include <stdexcept>
12#include <thread>
13
14#include "bsp_gpio.hpp"
15#include "bsp_spi.hpp"
16
17/* MAX7219 LED matrix driver controller template class
18 * N: Number of cascaded MAX7219 chips */
19template <size_t N>
20class Max7219 {
21 public:
22 /* Register address definitions */
23 static constexpr uint8_t REG_NOOP = 0x00;
24 static constexpr uint8_t REG_DIGIT0 = 0x01;
25 static constexpr uint8_t REG_DIGIT7 = 0x08;
26 static constexpr uint8_t REG_DECODE_MODE = 0x09;
27 static constexpr uint8_t REG_INTENSITY = 0x0A;
28 static constexpr uint8_t REG_SCAN_LIMIT = 0x0B;
29 static constexpr uint8_t REG_SHUTDOWN = 0x0C;
30 static constexpr uint8_t REG_DISPLAY_TEST = 0x0F;
31
32 /* Constructor: Initialize SPI and CS (Chip Select) pin */
33 Max7219(SpiDevice& spi, Gpio* cs) : spi_(spi), cs_(cs) {
34 if (!cs_) {
35 std::perror("CS GPIO is not initialized");
36 }
37 cs_->Write(1); /* CS active low, initialize to high */
38 for (auto& chip : framebuffer_) {
39 chip.fill(0); /* Clear frame buffer */
40 }
41
42 thread_ = std::thread(&Max7219::RefreshThread, this);
43
44 std::this_thread::sleep_for(std::chrono::milliseconds(100));
45
46 TestEachChip();
47 }
48
49 void RefreshThread() {
50 Initialize();
51
52 while (true) {
53 Refresh();
54 std::this_thread::sleep_for(std::chrono::milliseconds(5));
55 }
56 }
57
58 /* Initialize all cascaded chips */
59 void Initialize() {
60 for (size_t i = 0; i < N; ++i) {
61 WriteToChip(i, REG_SHUTDOWN, 0x00); /* Enter shutdown mode */
62 usleep(5); /* Short delay */
63 WriteToChip(i, REG_DISPLAY_TEST, 0x00); /* Normal operation */
64 WriteToChip(i, REG_DECODE_MODE, 0x00); /* Matrix mode (no decoding) */
65 WriteToChip(i, REG_SCAN_LIMIT, 0x07); /* Scan all 8 rows */
66 WriteToChip(i, REG_INTENSITY, 0x03); /* Medium brightness */
67 WriteToChip(i, REG_SHUTDOWN, 0x01); /* Normal operation */
68 }
69 Clear();
70 Refresh();
71 }
72
73 /* Set global brightness (0-15) */
74 void SetIntensity(uint8_t value) {
75 mutex_.lock();
76 if (value > 0x0F) {
77 value = 0x0F;
78 }
79 WriteAll(REG_INTENSITY, value);
80 mutex_.unlock();
81 }
82
83 /* Clear frame buffer */
84 void Clear() {
85 for (auto& chip : framebuffer_) {
86 chip.fill(0);
87 }
88 }
89
90 /* Set individual pixel state
91 * chip_index: Chip index (0-based)
92 * row: Vertical position (0-7)
93 * col: Horizontal position (0-7) */
94 void DrawPixel(size_t chip_index, uint8_t row, uint8_t col, bool on) {
95 if (chip_index >= N || row >= 8 || col >= 8) {
96 return;
97 }
98 if (on) {
99 framebuffer_[chip_index][7 - row] |= (1 << col);
100 } else {
101 framebuffer_[chip_index][7 - row] &= ~(1 << col);
102 }
103 }
104
105 /* 16x32 composite matrix drawing function
106 * Handles coordinate mapping for 4x2 matrix layout */
107 void DrawPixelMatrix2(uint8_t row, uint8_t col, bool on) {
108 if (row >= 16 || col >= 32) {
109 return;
110 }
111
112 /* Serpentine layout chip index mapping */
113 static constexpr int CHIP_INDEX_MAP[] = {0, 2, 1, 3};
114
115 /* Calculate chip position in virtual matrix */
116 size_t chip_index = (row / 8) + (col / 8) * 2;
117 /* Apply physical layout mapping */
118 chip_index = CHIP_INDEX_MAP[chip_index % 4] + (chip_index - chip_index % 4);
119 uint8_t local_row = row % 8;
120 uint8_t local_col = col % 8;
121
122 DrawPixel(chip_index, local_row, local_col, on);
123 }
124
125 /* Refresh display with current buffer */
126 void Refresh() {
127 mutex_.lock();
128 for (uint8_t row = 0; row < 8; ++row) {
129 std::array<uint8_t, N> regs;
130 std::array<uint8_t, N> data;
131 regs.fill(REG_DIGIT0 + row);
132 for (size_t i = 0; i < N; ++i) {
133 data[i] = framebuffer_[i][row];
134 }
135 WriteCommandRaw(regs, data);
136 }
137 mutex_.unlock();
138 }
139
140 /* Write to specific chip */
141 void WriteToChip(size_t index, uint8_t addr, uint8_t data) {
142 std::array<uint8_t, N> regs;
143 std::array<uint8_t, N> data_all;
144 regs.fill(REG_NOOP);
145 data_all.fill(0x00);
146 if (index >= N) {
147 return;
148 }
149 regs[index] = addr;
150 data_all[index] = data;
151 WriteCommandRaw(regs, data_all);
152 }
153
154 /* Full diagnostic test pattern */
155 void TestEachChip() {
156 Clear();
157 /* Draw left border */
158 for (int i = 0; i < 16; ++i) {
159 DrawPixelMatrix2(i, 0, true);
160 std::this_thread::sleep_for(std::chrono::milliseconds(5));
161 }
162
163 /* Draw bottom border */
164 for (int i = 0; i < 16; ++i) {
165 DrawPixelMatrix2(15, i, true);
166 std::this_thread::sleep_for(std::chrono::milliseconds(5));
167 }
168
169 /* Draw middle vertical line */
170 for (int i = 0; i < 16; ++i) {
171 DrawPixelMatrix2(i, 16, true);
172 std::this_thread::sleep_for(std::chrono::milliseconds(5));
173 }
174
175 /* Draw right border */
176 for (int i = 16; i < 32; ++i) {
177 DrawPixelMatrix2(15, i, true);
178 std::this_thread::sleep_for(std::chrono::milliseconds(5));
179 }
180
181 /* Draw right vertical line */
182 for (int i = 15; i > 0; --i) {
183 DrawPixelMatrix2(i, 31, true);
184 std::this_thread::sleep_for(std::chrono::milliseconds(5));
185 }
186
187 /* Draw top right border */
188 for (int i = 31; i > 16; --i) {
189 DrawPixelMatrix2(0, i, true);
190 std::this_thread::sleep_for(std::chrono::milliseconds(5));
191 }
192
193 /* Draw middle horizontal line */
194 for (int i = 15; i > 0; --i) {
195 DrawPixelMatrix2(i, 15, true);
196 std::this_thread::sleep_for(std::chrono::milliseconds(5));
197 }
198
199 /* Draw top left border */
200 for (int i = 15; i > 0; --i) {
201 DrawPixelMatrix2(0, i, true);
202 std::this_thread::sleep_for(std::chrono::milliseconds(5));
203 }
204 }
205
206 void Lock(){
207 mutex_.lock();
208 }
209
210 void Unlock(){
211 mutex_.unlock();
212 }
213
214 void SetLight(uint8_t light){
215 SetIntensity(light);
216 }
217
218 private:
219 // NOLINTNEXTLINE
220 SpiDevice& spi_;
221 Gpio* cs_;
222 std::array<std::array<uint8_t, 8>, N> framebuffer_;
223 std::thread thread_; /* Thread */
224 std::mutex mutex_;
225
226 /* Write to all chips with same register */
227 void WriteAll(uint8_t addr, uint8_t value) {
228 std::array<uint8_t, N> data;
229 data.fill(value);
230 WriteCommand(addr, data);
231 }
232
233 /* Command write wrapper */
234 void WriteCommand(uint8_t addr, const std::array<uint8_t, N>& data) {
235 std::array<uint8_t, N> regs;
236 regs.fill(addr);
237 WriteCommandRaw(regs, data);
238 }
239
240 /* Low-level SPI write operation */
241 void WriteCommandRaw(const std::array<uint8_t, N>& regs,
242 const std::array<uint8_t, N>& data) {
243 std::array<uint8_t, N * 2> tx_buf{};
244 for (size_t i = 0; i < N; ++i) {
245 const size_t HW_INDEX = N - 1 - i;
246 tx_buf[i * 2] = regs[HW_INDEX];
247 tx_buf[i * 2 + 1] = data[HW_INDEX];
248 }
249
250 spi_ioc_transfer transfer{};
251 transfer.tx_buf = reinterpret_cast<uint64_t>(tx_buf.data());
252 transfer.len = tx_buf.size();
253 transfer.speed_hz = 1000000;
254 transfer.bits_per_word = 8;
255 transfer.delay_usecs = 10;
256
257 usleep(100);
258 cs_->Write(0);
259 usleep(100);
260#ifndef TEST_BUILD
261 if (ioctl(spi_.Fd(), SPI_IOC_MESSAGE(1), &transfer) < 0) {
262 cs_->Write(1);
263 std::perror("SPI transfer failed");
264 }
265#endif
266
267 usleep(100);
268 cs_->Write(1);
269 usleep(100);
270 }
271};
void Write(int value)
Definition bsp_gpio.hpp:82