This repository has been archived by the owner on Aug 27, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 199
/
display_ssd1306.c
239 lines (192 loc) · 7.43 KB
/
display_ssd1306.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/** \file
\brief Code specific to the SSD1306 display.
*/
/**
D'oh. Shortly before completing this code, the display hardware died. Shows
just black, for whatever reason. Accordingly I can't test new code any longer
and writing code without seeing what it does makes no sense.
What already works:
- I2C with a queue for small transmissions. Sufficient to queue up sending
a rendered character. It's filled by displaybus_write() and drained by
the I2C interrupt. Larger transmissions are handled fine, too, but cause
wait cycles.
- 128 byte queue holding characters to send. This queue is filled by
display_writechar(). It's drained by display_tick(), which processes,
renders and forwards these characters to the I2C queue.
- Display initialisation.
- Clearing the display.
- Writing text with display_writestr_P().
- Writing formatted text with sendf_P(display_writechar, ...).
- Current state of code should clear the display at startup, show a
greeting message and start displaying current X/Y/Z coordinates, updated
once per second. All this not in a particularly pretty fashion, but
working.
TODO list:
- Lot's of prettification. Like a nice background picture with the Teacup
logo, like "Welcome to Teacup" as a greeting screen, like writing numbers
to readable places and so on.
- Allow different fonts. Already paraphrased in font.h and font.c. Needs
a selection menu in Configtool, of course, the same way one can select
display types.
- It's a bit unclear wether this 'last_byte' flag to displaybus_write() is
really ideal. Fact is, I2C transmissions need a start and an explicite
ending. Also thinkable would be a displaybus_finalise() function
which puts the marker in place. Saves a lot of shuffling parameters
around.
Yet another option would be to make sure the I2C send buffer is drained
befpre sending the next transmission. I2C code already finalises a
transmission on buffer drain, so only _reliable_ waiting needs an
implementation.
Each variant needs testing, which one gets away with the smallest code.
Smallest code is likely the fastest code as well.
- Here's an assistant to convert pictures/bitmaps into C code readable by
the compiler: http://en.radzio.dxp.pl/bitmap_converter/
*/
#include "display.h"
#if defined TEACUP_C_INCLUDE && defined DISPLAY_TYPE_SSD1306
#include "displaybus.h"
#include "font.h"
#include "sendf.h"
#include "delay.h"
#include "dda.h"
static const uint8_t PROGMEM init_sequence[] = {
0x00, // Command marker.
0xAE, // Display off.
0xD5, 0x80, // Display clock divider (reset).
0xA8, 0x1F, // 1/32 duty.
0x40 | 0x00, // Start line (reset).
0x20, 0x02, // Page addressing mode (reset).
0x22, 0x00, 0x03, // Start and end page in horiz./vert. addressing mode[1].
0x21, 0x00, 0x7F, // Start and end column in horiz./vert. addressing mode.
0xA0 | 0x00, // No segment remap (reset).
0xC0 | 0x00, // Normal com pins mapping (reset).
0xDA, 0x02, // Sequental without remap com pins.
0x81, 0x7F, // Contrast (reset).
0xDB, 0x20, // Vcomh (reset).
0xD9, 0xF1, // Precharge period.
0x8D, 0x14, // Charge pump.
0xA6, // Positive display.
0xA4, // Resume display.
0xAF // Display on.
};
// [1] Do not set this to 0x00..0x07 on a 32 pixel high display, or vertical
// addressing mode will mess up. 32 pixel high displays have only 4 pages
// (0..3), still addressing logic accepts, but can't deal with the 0..7
// meant for 64 pixel high displays.
/**
* Initializes the display's controller configuring the way of
* displaying data.
*/
void display_init(void) {
uint8_t i;
displaybus_init(DISPLAY_I2C_ADDRESS);
for (i = 0; i < sizeof(init_sequence); i++) {
// Send last byte with 'last_byte' set.
displaybus_write(init_sequence[i], (i == sizeof(init_sequence) - 1));
}
}
/**
Show a nice greeting. Pure eye candy.
*/
void display_greeting(void) {
display_clear();
/**
"Welcome to Teacup" is 64 pixel columns wide, entire display is
128 columns, so we offset by 32 columns to get it to the center.
*/
display_set_cursor(1, 32);
display_writestr_P(PSTR("Welcome to Teacup"));
// Forward this to the display immediately.
while (buf_canread(display)) {
display_tick();
}
// Allow the user to worship our work for a moment :-)
delay_ms(5000);
}
/**
Regular update of the display. Typically called once a second from clock.c.
*/
void display_clock(void) {
display_set_cursor(0, 2);
update_current_position();
sendf_P(display_writechar, PSTR("X:%lq Y:%lq Z:%lq F:%lu "),
current_position.axis[X], current_position.axis[Y],
current_position.axis[Z], current_position.F);
}
/**
Forwards a character or a control command from the display queue to the I2C
queue.
*/
void display_tick() {
uint16_t i, data, index;
if (displaybus_busy()) {
return;
}
/**
Possible strategy for error recovery: after a failed, aborted I2C
transmisson, 'i2c_state & I2C_INTERRUPTED' in i2c.c evaluates to true.
Having a getter like displaybus_failed() would allow to test this condition
here, so we could resend the previous data again, instead of grabbing a
new byte from the buffer.
*/
if (buf_canread(display)) {
buf_pop(display, data);
switch (data) {
case low_code_clear:
/**
Clear the screen. As this display supports many sophisticated
commands, but not a simple 'clear', we have to overwrite the entire
memory with zeros, byte by byte.
*/
// Set horizontal adressing mode.
displaybus_write(0x00, 0);
displaybus_write(0x20, 0);
displaybus_write(0x00, 1);
// Write 512 zeros.
displaybus_write(0x40, 0);
for (i = 0; i < 512; i++) {
displaybus_write(0x00, (i == 511));
}
// Return to page adressing mode.
displaybus_write(0x00, 0);
displaybus_write(0x20, 0);
displaybus_write(0x02, 1);
break;
case low_code_set_cursor:
/**
Set the cursor to the given position.
This is a three-byte control command, so we fetch additional bytes
from the queue and cross fingers they're actually there.
*/
// Enter command mode.
displaybus_write(0x00, 0);
// Set line.
buf_pop(display, data);
displaybus_write(0xB0 | (data & 0x03), 0);
// Set column.
buf_pop(display, data);
displaybus_write(0x00 | (data & 0x0F), 0);
displaybus_write(0x10 | ((data >> 4) & 0x0F), 1);
break;
default:
// Should be a printable character.
index = data - 0x20;
// Write pixels command.
displaybus_write(0x40, 0);
// Send the character bitmap.
#ifdef FONT_IS_PROPORTIONAL
for (i = 0; i < pgm_read_byte(&font[index].columns); i++) {
#else
for (i = 0; i < FONT_COLUMNS; i++) {
#endif
displaybus_write(pgm_read_byte(&font[index].data[i]), 0);
}
// Send space between characters.
for (i = 0; i < FONT_SYMBOL_SPACE; i++) {
displaybus_write(0x00, (i == FONT_SYMBOL_SPACE - 1));
}
break;
}
}
}
#endif /* TEACUP_C_INCLUDE && DISPLAY_TYPE_SSD1306 */