Skip to content

Commit 9c68f06

Browse files
authored
Merge pull request #507 from juhdanad/hd44780-opengl-mutex
Fix data race when rendering an LCD display
2 parents ae75ede + 68cf848 commit 9c68f06

File tree

7 files changed

+185
-28
lines changed

7 files changed

+185
-28
lines changed

examples/board_hd44780/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ LDFLAGS += -lpthread
3333

3434
include ../Makefile.opengl
3535

36-
all: obj atmega48_charlcd.axf ${target}
36+
all: obj atmega48_charlcd.axf atmega48_fps_test.axf ${target}
3737

3838
atmega48_charlcd.axf: atmega48_charlcd.c
39+
atmega48_fps_test.axf: atmega48_fps_test.c
3940

4041
include ${simavr}/Makefile.common
4142

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#undef F_CPU
2+
#define F_CPU 16000000
3+
4+
#include <avr/io.h>
5+
6+
#include "avr_mcu_section.h"
7+
AVR_MCU(F_CPU, "atmega48");
8+
9+
#include "avr_hd44780.c"
10+
11+
int main()
12+
{
13+
hd44780_init();
14+
/*
15+
* Clear the display.
16+
*/
17+
hd44780_outcmd(HD44780_CLR);
18+
hd44780_wait_ready(1); // long wait
19+
20+
/*
21+
* Entry mode: auto-increment address counter, no display shift in
22+
* effect.
23+
*/
24+
hd44780_outcmd(HD44780_ENTMODE(1, 0));
25+
hd44780_wait_ready(0);
26+
27+
/*
28+
* Enable display, activate non-blinking cursor.
29+
*/
30+
hd44780_outcmd(HD44780_DISPCTL(1, 1, 0));
31+
hd44780_wait_ready(0);
32+
33+
uint16_t count = 0;
34+
while (1)
35+
{
36+
uint16_t temp = count;
37+
for (uint8_t i = 5; i > 0; i--)
38+
{
39+
hd44780_outcmd(HD44780_DDADDR(i - 1));
40+
hd44780_wait_ready(0);
41+
hd44780_outdata(temp % 10 + 48);
42+
temp /= 10;
43+
hd44780_wait_ready(0);
44+
}
45+
count++;
46+
}
47+
}

examples/board_hd44780/charlcd.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,15 @@ main(
146146
char *argv[])
147147
{
148148
elf_firmware_t f = {{0}};
149-
const char * fname = "atmega48_charlcd.axf";
149+
const char *fname = argc > 1 ? argv[1] : "atmega48_charlcd.axf";
150150
// char path[256];
151151
// sprintf(path, "%s/%s", dirname(argv[0]), fname);
152152
// printf("Firmware pathname is %s\n", path);
153-
elf_read_firmware(fname, &f);
153+
if (elf_read_firmware(fname, &f) == -1)
154+
{
155+
fprintf(stderr, "Unable to load firmware from file %s\n", fname);
156+
exit(EXIT_FAILURE);
157+
};
154158

155159
printf("firmware %s f=%d mmcu=%s\n", fname, (int) f.frequency, f.mmcu);
156160

@@ -168,6 +172,8 @@ main(
168172

169173
hd44780_init(avr, &hd44780, 20, 4);
170174

175+
hd44780_setup_mutex_for_gl(&hd44780);
176+
171177
/* Connect Data Lines to Port B, 0-3 */
172178
/* These are bidirectional too */
173179
for (int i = 0; i < 4; i++) {

examples/parts/hd44780.c

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ _hd44780_busy_timer(
7676
{
7777
hd44780_t *b = (hd44780_t *) param;
7878
// printf("%s called\n", __FUNCTION__);
79-
hd44780_set_flag(b, HD44780_FLAG_BUSY, 0);
79+
hd44780_set_private_flag(b, HD44780_PRIV_FLAG_BUSY, 0);
8080
avr_raise_irq(b->irq + IRQ_HD44780_BUSY, 0);
8181
return 0;
8282
}
@@ -189,7 +189,7 @@ hd44780_write_command(
189189
hd44780_set_flag(b, HD44780_FLAG_F, b->datapins & 4);
190190
if (!four && !hd44780_get_flag(b, HD44780_FLAG_D_L)) {
191191
printf("%s activating 4 bits mode\n", __FUNCTION__);
192-
hd44780_set_flag(b, HD44780_FLAG_LOWNIBBLE, 0);
192+
hd44780_set_private_flag(b, HD44780_PRIV_FLAG_LOWNIBBLE, 0);
193193
}
194194
} break;
195195
// Cursor display shift
@@ -231,7 +231,7 @@ hd44780_process_write(
231231
{
232232
uint32_t delay = 0; // uS
233233
int four = !hd44780_get_flag(b, HD44780_FLAG_D_L);
234-
int comp = four && hd44780_get_flag(b, HD44780_FLAG_LOWNIBBLE);
234+
int comp = four && hd44780_get_private_flag(b, HD44780_PRIV_FLAG_LOWNIBBLE);
235235
int write = 0;
236236

237237
if (four) { // 4 bits !
@@ -240,7 +240,7 @@ hd44780_process_write(
240240
else
241241
b->datapins = (b->datapins & 0xf) | ((b->pinstate >> (IRQ_HD44780_D4-4)) & 0xf0);
242242
write = comp;
243-
b->flags ^= (1 << HD44780_FLAG_LOWNIBBLE);
243+
b->private_flags ^= (1 << HD44780_PRIV_FLAG_LOWNIBBLE);
244244
} else { // 8 bits
245245
b->datapins = (b->pinstate >> IRQ_HD44780_D0) & 0xff;
246246
write++;
@@ -249,13 +249,15 @@ hd44780_process_write(
249249

250250
// write has 8 bits to process
251251
if (write) {
252-
if (hd44780_get_flag(b, HD44780_FLAG_BUSY)) {
252+
if (hd44780_get_private_flag(b, HD44780_PRIV_FLAG_BUSY)) {
253253
printf("%s command %02x write when still BUSY\n", __FUNCTION__, b->datapins);
254254
}
255+
hd44780_lock_state(b);
255256
if (b->pinstate & (1 << IRQ_HD44780_RS)) // write data
256257
delay = hd44780_write_data(b);
257258
else // write command
258259
delay = hd44780_write_command(b);
260+
hd44780_unlock_state(b);
259261
}
260262
return delay;
261263
}
@@ -266,42 +268,44 @@ hd44780_process_read(
266268
{
267269
uint32_t delay = 0; // uS
268270
int four = !hd44780_get_flag(b, HD44780_FLAG_D_L);
269-
int comp = four && hd44780_get_flag(b, HD44780_FLAG_LOWNIBBLE);
271+
int comp = four && hd44780_get_private_flag(b, HD44780_PRIV_FLAG_LOWNIBBLE);
270272
int done = 0; // has something on the datapin we want
271273

272274
if (comp) {
273275
// ready the 4 final bits on the 'actual' lcd pins
274276
b->readpins <<= 4;
275277
done++;
276-
b->flags ^= (1 << HD44780_FLAG_LOWNIBBLE);
278+
b->private_flags ^= (1 << HD44780_PRIV_FLAG_LOWNIBBLE);
277279
}
278280

279281
if (!done) { // new read
280282

281283
if (b->pinstate & (1 << IRQ_HD44780_RS)) { // read data
282284
delay = 37;
283285
b->readpins = b->vram[b->cursor];
286+
hd44780_lock_state(b);
284287
hd44780_kick_cursor(b);
288+
hd44780_unlock_state(b);
285289
} else { // read 'command' ie status register
286290
delay = 0; // no raising busy when reading busy !
287291

288292
// low bits are the current cursor
289293
b->readpins = b->cursor < 0x80 ? b->cursor : b->cursor-0x80;
290-
int busy = hd44780_get_flag(b, HD44780_FLAG_BUSY);
294+
int busy = hd44780_get_private_flag(b, HD44780_PRIV_FLAG_BUSY);
291295
b->readpins |= busy ? 0x80 : 0;
292296

293297
// if (busy) printf("Good boy, guy's reading status byte\n");
294298
// now that we're read the busy flag, clear it and clear
295299
// the timer too
296-
hd44780_set_flag(b, HD44780_FLAG_BUSY, 0);
300+
hd44780_set_private_flag(b, HD44780_PRIV_FLAG_BUSY, 0);
297301
avr_raise_irq(b->irq + IRQ_HD44780_BUSY, 0);
298302
avr_cycle_timer_cancel(b->avr, _hd44780_busy_timer, b);
299303
}
300304
avr_raise_irq(b->irq + IRQ_HD44780_DATA_OUT, b->readpins);
301305

302306
done++;
303307
if (four)
304-
b->flags |= (1 << HD44780_FLAG_LOWNIBBLE); // for next read
308+
b->private_flags |= (1 << HD44780_PRIV_FLAG_LOWNIBBLE); // for next read
305309
}
306310

307311
// now send the prepared output pins to send as IRQs
@@ -320,7 +324,7 @@ _hd44780_process_e_pinchange(
320324
{
321325
hd44780_t *b = (hd44780_t *) param;
322326

323-
hd44780_set_flag(b, HD44780_FLAG_REENTRANT, 1);
327+
hd44780_set_private_flag(b, HD44780_PRIV_FLAG_REENTRANT, 1);
324328

325329
#if 0
326330
uint16_t touch = b->oldstate ^ b->pinstate;
@@ -338,13 +342,13 @@ _hd44780_process_e_pinchange(
338342
delay = hd44780_process_write(b);
339343

340344
if (delay) {
341-
hd44780_set_flag(b, HD44780_FLAG_BUSY, 1);
345+
hd44780_set_private_flag(b, HD44780_PRIV_FLAG_BUSY, 1);
342346
avr_raise_irq(b->irq + IRQ_HD44780_BUSY, 1);
343347
avr_cycle_timer_register_usec(b->avr, delay,
344348
_hd44780_busy_timer, b);
345349
}
346350
// b->oldstate = b->pinstate;
347-
hd44780_set_flag(b, HD44780_FLAG_REENTRANT, 0);
351+
hd44780_set_private_flag(b, HD44780_PRIV_FLAG_REENTRANT, 0);
348352
return 0;
349353
}
350354

@@ -373,7 +377,7 @@ hd44780_pin_changed_hook(
373377
return; // job already done!
374378
case IRQ_HD44780_D0 ... IRQ_HD44780_D7:
375379
// don't update these pins in read mode
376-
if (hd44780_get_flag(b, HD44780_FLAG_REENTRANT))
380+
if (hd44780_get_private_flag(b, HD44780_PRIV_FLAG_REENTRANT))
377381
return;
378382
break;
379383
}

examples/parts/hd44780.h

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,23 @@ enum {
7979
HD44780_FLAG_S, // 1: Follow display shift
8080
HD44780_FLAG_I_D, // 1: Increment, 0: Decrement
8181

82-
/*
83-
* Internal flags, not HD44780
84-
*/
85-
HD44780_FLAG_LOWNIBBLE, // 1: 4 bits mode, write/read low nibble
86-
HD44780_FLAG_BUSY, // 1: Busy between instructions, 0: ready
87-
HD44780_FLAG_REENTRANT, // 1: Do not update pins
88-
82+
/*
83+
* Internal flags, not HD44780
84+
*/
8985
HD44780_FLAG_DIRTY, // 1: needs redisplay...
9086
HD44780_FLAG_CRAM_DIRTY, // 1: Character memory has changed
9187
};
9288

89+
/*
90+
* Private internal flags. These are not protected by
91+
* callbacks, so other threads must not access them.
92+
*/
93+
enum {
94+
HD44780_PRIV_FLAG_BUSY = 0, // 1: Busy between instructions, 0: ready
95+
HD44780_PRIV_FLAG_LOWNIBBLE, // 1: 4 bits mode, write/read low nibble
96+
HD44780_PRIV_FLAG_REENTRANT, // 1: Do not update pins
97+
};
98+
9399

94100
typedef struct hd44780_t
95101
{
@@ -106,6 +112,21 @@ typedef struct hd44780_t
106112
uint8_t readpins;
107113

108114
uint16_t flags; // LCD flags ( HD44780_FLAG_*)
115+
// LCD private flags, not protected by callbacks ( HD44780_PRIV_FLAG_*)
116+
// You must not use these flags from a different thread than the simavr one.
117+
uint8_t private_flags;
118+
119+
// These callbacks are called before and after a data or
120+
// command packet is sent to the unit and as a
121+
// result the internal state of the device changes.
122+
// If you lock and unlock a mutex in these, you can guard
123+
// the cursor, vram and flags variables with it.
124+
// The avr thread reads these variables outside
125+
// of these functions, but writes always happen between them.
126+
void *on_state_lock_parameter;
127+
void (*on_state_lock)(void *);
128+
void *on_state_unlock_parameter;
129+
void (*on_state_unlock)(void *);
109130
} hd44780_t;
110131

111132
void
@@ -134,4 +155,34 @@ hd44780_get_flag(
134155
return (b->flags & (1 << bit)) != 0;
135156
}
136157

158+
static inline int
159+
hd44780_set_private_flag(
160+
hd44780_t *b, uint16_t bit, int val)
161+
{
162+
int old = b->private_flags & (1 << bit);
163+
b->private_flags = (b->private_flags & ~(1 << bit)) | (val ? (1 << bit) : 0);
164+
return old != 0;
165+
}
166+
167+
static inline int
168+
hd44780_get_private_flag(
169+
hd44780_t *b, uint16_t bit)
170+
{
171+
return (b->private_flags & (1 << bit)) != 0;
172+
}
173+
174+
static inline void
175+
hd44780_lock_state(hd44780_t *b)
176+
{
177+
if (b->on_state_lock)
178+
(*(b->on_state_lock))(b->on_state_lock_parameter);
179+
}
180+
181+
static inline void
182+
hd44780_unlock_state(hd44780_t *b)
183+
{
184+
if (b->on_state_unlock)
185+
(*(b->on_state_unlock))(b->on_state_unlock_parameter);
186+
}
187+
137188
#endif

examples/parts/hd44780_glut.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,35 @@
2727
#else
2828
#include <GL/glut.h>
2929
#endif
30+
#include <pthread.h>
31+
#include <stdio.h>
3032

3133
#include "lcd_font.h" // generated with gimp
3234

3335
static GLuint font_texture;
3436
static int charwidth = 5;
3537
static int charheight = 7;
3638

39+
static pthread_mutex_t hd44780_state_mutex = PTHREAD_MUTEX_INITIALIZER;
40+
41+
void before_state_lock_cb(void *b)
42+
{
43+
pthread_mutex_lock(&hd44780_state_mutex);
44+
}
45+
46+
void after_state_lock_cb(void *b)
47+
{
48+
pthread_mutex_unlock(&hd44780_state_mutex);
49+
}
50+
51+
void hd44780_setup_mutex_for_gl(hd44780_t *b)
52+
{
53+
b->on_state_lock = &before_state_lock_cb;
54+
b->on_state_lock_parameter = b;
55+
b->on_state_unlock = &after_state_lock_cb;
56+
b->on_state_unlock_parameter = b;
57+
}
58+
3759
void
3860
hd44780_gl_init()
3961
{
@@ -123,6 +145,13 @@ hd44780_gl_draw(
123145
uint32_t text,
124146
uint32_t shadow)
125147
{
148+
if (b->on_state_lock != &before_state_lock_cb ||
149+
b->on_state_unlock != &after_state_lock_cb)
150+
{
151+
printf("Error: the hd44780 instance is not using the mutex of the OpenGL thread!\nCall hd44780_setup_mutex_for_gl() first!\n");
152+
exit(EXIT_FAILURE);
153+
}
154+
126155
int rows = b->w;
127156
int lines = b->h;
128157
int border = 3;
@@ -139,16 +168,27 @@ hd44780_gl_draw(
139168
+ (lines - 1) + border, 0);
140169
glEnd();
141170

171+
// create a local copy (so we can release the mutex as fast as possible)
172+
uint8_t vram[192];
173+
pthread_mutex_lock(&hd44780_state_mutex);
174+
// cgram is not updated yet
175+
// int cgram_dirty = hd44780_get_flag(b, HD44780_FLAG_CRAM_DIRTY);
176+
for (uint8_t i = 0; i < 192; i++)
177+
vram[i] = b->vram[i];
178+
// the values have been seen, they are not dirty anymore
179+
hd44780_set_flag(b, HD44780_FLAG_CRAM_DIRTY, 0);
180+
hd44780_set_flag(b, HD44780_FLAG_DIRTY, 0);
181+
pthread_mutex_unlock(&hd44780_state_mutex);
182+
142183
glColor3f(1.0f, 1.0f, 1.0f);
143184
const uint8_t offset[] = { 0x00, 0x40, 0x00 + 20, 0x40 + 20 };
144-
for (int v = 0 ; v < b->h; v++) {
185+
for (int v = 0 ; v < lines; v++) {
145186
glPushMatrix();
146-
for (int i = 0; i < b->w; i++) {
147-
glputchar(b->vram[offset[v] + i], character, text, shadow);
187+
for (int i = 0; i < rows; i++) {
188+
glputchar(vram[offset[v] + i], character, text, shadow);
148189
glTranslatef(6, 0, 0);
149190
}
150191
glPopMatrix();
151192
glTranslatef(0, 8, 0);
152193
}
153-
hd44780_set_flag(b, HD44780_FLAG_DIRTY, 0);
154194
}

0 commit comments

Comments
 (0)