Difference between revisions of "Böpdisplayen"

From Chalmers Robotförening
(Created page with "En sida om den omtalade böpdisplayen!")
 
m
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
En sida om den omtalade böpdisplayen!
En sida om den omtalade böpdisplayen!
[[File:display.jpg|thumb|400px|Böpdisplayen]]
== Uppbyggnad/Displaygränssnitt ==
Displayen är uppbyggd av ett flertal seriekopplade [[wikipedia:Skiftregister|skiftregister]] där varje databit bestämmer om ett segment i en siffra skall vara tänt (1) eller släckt (0). Eftersom varje siffra endast består av 7 segment och varje skiftregister har 8 utgångar krävs det att man efter att ha skiftat ut varje siffras på/av-segment (totalt 7 bitar) även skiftar ut en "dummybit" för att alla bitar ska stämma överens med respektive segment.
Skiftregisterna är av typen MC74HC4094A ([https://www.onsemi.com/pub/Collateral/MC74HC4094A-D.PDF|datablad])
== Inbyggd Styrenhet ==
Displayen har en egen inbyggd styrenhet som sköter uppdatering av innehållet. Den är byggd med en Arduino Nano.
=== Kommunikation ===
Kommunikation med den inbyggda styrenheten sker via en enkelriktad (simplex) seriell länk med följande specifikationer:
* Hastighet: 115200 baud
* Databitar: 8 st
* Paritetetsbitar: Inga (None)
* Stopbitar: 1 st
Detta motsvarar upplägget 8N1 och är standard i Arduinobiblioteket.
Den seriella länken finns tillgänglig via en BNC-kontakt på styrenheten (yttre = jord, inre = signal).
OBS! Eftersom Arduino Nano:n kör med spänningen 5 Volt är det viktigt att inte skicka in mer än det via serielänken!
=== Kommandon ===
Styrenheten accepterar flera kommandon via den seriella länken. Gemensamt är att de alla består av ett kommandotecken följt av en eller flera datatecken (x). Accepterade datatecken är siffrorna 0-9, alla andra tecken behandlas som blanksteg och ger en tom siffra.
* '''Sxxxxxxxx''' – uppdaterar hela displayen med nya datatecken xxxxxxxx.
* '''[A-H]x''' – uppdaterar motsvarande displaysiffra [A-H, se bild (W.I.P.)] med innehållet som direkt följer bokstaven.
* '''L''' – ställer in längden på hur många siffror som uppdateras när ett I-kommando tas emot. Uppstartsvärdet (default) är 8 och innebär att I-kommandot alltid skriver över alla siffror och genererar inledande nollor. Antalet siffror som önskas följer L i kommandosekvensen. För att styrenheten skall veta när alla siffror tagits emot krävs det att det sista tecknet ej är en siffra.
* '''I[A-H]''' – skriver ett heltal (integer) till displayen med start på det angivna segmentet. Antalet segment som uppdateras anges via L-kommandot. Heltalets siffror följer I i kommandosekvensen och även här krävs det att det sista tecknet ej är en siffra.
* '''W''' – inaktiverar automatisk uppdatering. I vanliga fall uppdateras displayen automatiskt när något av kommandona som ändrar innehållet mottagits, men efter detta kommando behöver det göras manuellt med R-kommandot. Detta läge är bra då man tänker göra många små uppdateringar innan allt innehåll är färdigskickat och minskar "flicker" på displayen.
* '''Q''' – aktiverar automatisk uppdatering.
* '''R''' – uppdaterar den fysiska displayen med det lagrade segmentinnehållet (alltså en manuell uppdatering).
Exempel: Du vill skriva ut tid i formatet HH:MM:SS med start vid den tredje siffran (alltså högerjusterat). Först görs följande initiala inställningar:
# '''W''' - inaktivera automatisk uppdatering
# '''Sxxxxxxxx''' - tömmer displayen (''x'' tolkas som ett mellanslag, kan vara vilket annat tecken som ej är en siffra här så länge det är '''åtta''' stycken)
# '''L2x''' - ställer in I-kommandot för två siffor. ''x'' är återigen ett tecken som inte är en siffra.
Varje gång du vill uppdatera med ny tid används följande kommandon:
# '''ICHHx''' - skriver antalet timmar med start på siffra C (den tredje), HH kan även vara endast H då antalet timmar är < 10. Inledande nollor sätts in automatiskt av styrenheten.
# '''IEMMx''' - samma som ovan fast för minuter
# '''IGSSx''' - samma som ovan fast för sekunder
# '''R''' - skriver ut det mottagna innehållet på displayen
=== Hårdvara ===
[[File:backside_controller.jpg|thumb|400px| Pinout för styrenheten]]
På bilden till höger syns hur kontakterna på styrkortet är kopplade. Uppe till vänster kommer +12V och omvandlas till +5V för displayen och styrenheten. Displayen ansluts och styrs med kontakten uppe till höger. Kontakten längst ner till höger är till för kommunikation med styrenheten och bryter ut GND (jord), +5V, RX och TX från Arduino Nano:n. De seriella kommandona skickas till RX-pinnen (recieve), ingen data returneras för tillfället på TX-pinnen. Observera att styrenheten och apparaten som skickar kommandon måste sammankopplas via jord för att kommunikationen ska fungera. På bilden syns hur den monterade BNC-kontakten är inkopplad.
<div style="clear: both"></div>
=== Mjukvara ===
Mjukvaran, i form av en Arduinosketch, ges nedan.
<pre class="prettyprint mw-collapsible">
/*
Author: Anton Berneving
Copyright 2018
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense,and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to
do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included i
n all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// pin definitions and macros for the display interface
#define PIN_CLK PB1
#define PIN_DATA PB2
#define PIN_STB PB3
#define PIN_HIGH(x) PORTB |= (1 << (x))
#define PIN_LOW(x) PORTB &= ~(1 << (x))
// variables for keeping track of which state we are in
#define STATE_NONE 0
#define STATE_STRING 1
#define STATE_SINGLE 2
#define STATE_INTEGER 3
char current_state = STATE_NONE;
char serial_index = 0;
char integer_length = 8;
char auto_refresh = 1;
// table of characters for the display 0-9, A-Z
unsigned char digits[] = {
0b01111110, // zero
0b00011000, // one
0b00110111, // two
0b00111101, // three
0b01011001, // four
0b01101101, // five
0b01101111, // six
0b00111000, // seven
0b01111111, // eight
0b01111001, // nine
0b00000000, // empty
};
// stores the character in each place in the display
#define DISPLAY_DIGITS 8
unsigned char display_content[DISPLAY_DIGITS] =  {10, 10, 10, 10, 10, 10, 10, 10};
void setup() {
// initialize serial communication
Serial.begin(115200);
// set display pins as output
DDRB |= (1 << PIN_CLK) | (1 << PIN_DATA) | (1 << PIN_STB);
// initial states
PIN_LOW(PIN_CLK);
PIN_LOW(PIN_DATA);
PIN_LOW(PIN_STB);
# show all segments at startup
setDisplayContent(88888888, 0, 8);
refreshDisplayContent();
delay(2000);
clearDispForce(); 
}
void loop() {
// do serial communication
while(Serial.available() > 0){
// read a character
char data = Serial.read();
// take action based on current state
switch(current_state){
case STATE_NONE:
if(data >= 'A' && data <= 'H'){ // Single Edit Command
current_state = STATE_SINGLE;
serial_index = data - 'A';
} else if (data == 'S'){ // is this a "String" command?
// yes, move to correct state and reset the index counter
current_state = STATE_STRING;
serial_index = 0;
} else if (data == 'I'){ // "Integer" command
current_state = STATE_INTEGER;
} else if (data == 'R'){ // "Force Refresh" command
refreshDisplayContent();
} else if (data == 'L'){ // set integer length command
integer_length = Serial.parseInt();
} else if (data == 'W'){ // disable auto refresh command
auto_refresh = 0;
} else if (data == 'Q'){ // enable auto refresh command
auto_refresh = 1;
}
break;
case STATE_STRING:
// read input digit
data = data - '0';
if(data < 0 || data > 9) data = 10;
display_content[serial_index] = (unsigned char)data;
// increment index (move to next position) (this could all be done with pointers...)
serial_index++;
// if we are done filling all positions, apply change
if(serial_index >= DISPLAY_DIGITS){
current_state = STATE_NONE;
// update display
if(auto_refresh == 1)
refreshDisplayContent();
}
break;
case STATE_SINGLE:
// read input digit
data = data - '0';
if(data < 0 || data > 9) data = 10;
display_content[serial_index] = (unsigned char)data;
// no longer in "single" mode
current_state = STATE_NONE;
// apply changes
if(auto_refresh == 1)
refreshDisplayContent();
break;
case STATE_INTEGER:
if(data >= 'A' && data <= 'H'){
// this is where to start the integer (with leading zeros and configurable length)
serial_index = data - 'A';
long val = Serial.parseInt();
setDisplayContent(val, serial_index, integer_length);
if(auto_refresh == 1)
refreshDisplayContent();
}else
current_state = STATE_NONE;
break;
}   
}
}
// sets the display content as a number starting at index start_index with the specified length. Leading zeros are inserted automatically
void setDisplayContent(long number, char start_index, char length){
for(int i = start_index + length - 1; i >= start_index; i--){
// skip wrong indexes
if(i > 7 || i < 0)
continue;
display_content[i] = number % 10;
number /= 10;
}
}
// writes the characters of display_contents to the display
void refreshDisplayContent(){
//check if any of the characters is a space...
char hasSpace = 0;
for(int i = 0; i < 8; i++){
if(display_content[i] == 10){
hasSpace = 1;
break;
}
}
// if the contents contain a space, enable strobe line during the shift out (does not work properly otherwise)
if(hasSpace){
PIN_HIGH(PIN_STB);
}
// write the eight digits
shiftDisplayContent();
// strobe data
strobeDisp();
}
void shiftDisplayContent(){
for(int i = DISPLAY_DIGITS - 1; i >= 0; i--){
shift_out_char7(digits[display_content[i]], i == 0);
}
}
void strobeDisp(){
PIN_HIGH(PIN_STB);
PIN_LOW(PIN_STB);
}
// fill display with zeroes (note: this happens continuously on the display since STB must be active for writing all zeroes unfortunately...)
void clearDispForce(){
PIN_LOW(PIN_DATA);
PIN_HIGH(PIN_STB);
for(int i = 0; i < 8*DISPLAY_DIGITS; i++){
PIN_HIGH(PIN_CLK); 
PIN_LOW(PIN_CLK);
}
PIN_LOW(PIN_STB);
}
/*
Shift the lower 7 bits of the provided character to the display
*/
void shift_out_char7(unsigned char data, bool last){
for(unsigned char i = 0; i < (last ? 7 : 8); i++){
// write data
if(data & 0x01) // lsb bit == 1 ?
PIN_HIGH(PIN_DATA);
else
PIN_LOW(PIN_DATA);
// pulse clock
PIN_HIGH(PIN_CLK);
PIN_LOW(PIN_CLK);
// shift data
data = data >> 1;
}
}
</pre>
[[Category:Projekt]]

Latest revision as of 22:22, 30 August 2018

En sida om den omtalade böpdisplayen!

Böpdisplayen


Uppbyggnad/Displaygränssnitt

Displayen är uppbyggd av ett flertal seriekopplade skiftregister där varje databit bestämmer om ett segment i en siffra skall vara tänt (1) eller släckt (0). Eftersom varje siffra endast består av 7 segment och varje skiftregister har 8 utgångar krävs det att man efter att ha skiftat ut varje siffras på/av-segment (totalt 7 bitar) även skiftar ut en "dummybit" för att alla bitar ska stämma överens med respektive segment.

Skiftregisterna är av typen MC74HC4094A ([1])


Inbyggd Styrenhet

Displayen har en egen inbyggd styrenhet som sköter uppdatering av innehållet. Den är byggd med en Arduino Nano.

Kommunikation

Kommunikation med den inbyggda styrenheten sker via en enkelriktad (simplex) seriell länk med följande specifikationer:

  • Hastighet: 115200 baud
  • Databitar: 8 st
  • Paritetetsbitar: Inga (None)
  • Stopbitar: 1 st

Detta motsvarar upplägget 8N1 och är standard i Arduinobiblioteket. Den seriella länken finns tillgänglig via en BNC-kontakt på styrenheten (yttre = jord, inre = signal).

OBS! Eftersom Arduino Nano:n kör med spänningen 5 Volt är det viktigt att inte skicka in mer än det via serielänken!

Kommandon

Styrenheten accepterar flera kommandon via den seriella länken. Gemensamt är att de alla består av ett kommandotecken följt av en eller flera datatecken (x). Accepterade datatecken är siffrorna 0-9, alla andra tecken behandlas som blanksteg och ger en tom siffra.

  • Sxxxxxxxx – uppdaterar hela displayen med nya datatecken xxxxxxxx.
  • [A-H]x – uppdaterar motsvarande displaysiffra [A-H, se bild (W.I.P.)] med innehållet som direkt följer bokstaven.
  • L – ställer in längden på hur många siffror som uppdateras när ett I-kommando tas emot. Uppstartsvärdet (default) är 8 och innebär att I-kommandot alltid skriver över alla siffror och genererar inledande nollor. Antalet siffror som önskas följer L i kommandosekvensen. För att styrenheten skall veta när alla siffror tagits emot krävs det att det sista tecknet ej är en siffra.
  • I[A-H] – skriver ett heltal (integer) till displayen med start på det angivna segmentet. Antalet segment som uppdateras anges via L-kommandot. Heltalets siffror följer I i kommandosekvensen och även här krävs det att det sista tecknet ej är en siffra.
  • W – inaktiverar automatisk uppdatering. I vanliga fall uppdateras displayen automatiskt när något av kommandona som ändrar innehållet mottagits, men efter detta kommando behöver det göras manuellt med R-kommandot. Detta läge är bra då man tänker göra många små uppdateringar innan allt innehåll är färdigskickat och minskar "flicker" på displayen.
  • Q – aktiverar automatisk uppdatering.
  • R – uppdaterar den fysiska displayen med det lagrade segmentinnehållet (alltså en manuell uppdatering).

Exempel: Du vill skriva ut tid i formatet HH:MM:SS med start vid den tredje siffran (alltså högerjusterat). Först görs följande initiala inställningar:

  1. W - inaktivera automatisk uppdatering
  2. Sxxxxxxxx - tömmer displayen (x tolkas som ett mellanslag, kan vara vilket annat tecken som ej är en siffra här så länge det är åtta stycken)
  3. L2x - ställer in I-kommandot för två siffor. x är återigen ett tecken som inte är en siffra.

Varje gång du vill uppdatera med ny tid används följande kommandon:

  1. ICHHx - skriver antalet timmar med start på siffra C (den tredje), HH kan även vara endast H då antalet timmar är < 10. Inledande nollor sätts in automatiskt av styrenheten.
  2. IEMMx - samma som ovan fast för minuter
  3. IGSSx - samma som ovan fast för sekunder
  4. R - skriver ut det mottagna innehållet på displayen

Hårdvara

Pinout för styrenheten

På bilden till höger syns hur kontakterna på styrkortet är kopplade. Uppe till vänster kommer +12V och omvandlas till +5V för displayen och styrenheten. Displayen ansluts och styrs med kontakten uppe till höger. Kontakten längst ner till höger är till för kommunikation med styrenheten och bryter ut GND (jord), +5V, RX och TX från Arduino Nano:n. De seriella kommandona skickas till RX-pinnen (recieve), ingen data returneras för tillfället på TX-pinnen. Observera att styrenheten och apparaten som skickar kommandon måste sammankopplas via jord för att kommunikationen ska fungera. På bilden syns hur den monterade BNC-kontakten är inkopplad.


Mjukvara

Mjukvaran, i form av en Arduinosketch, ges nedan.

/*
	Author: Anton Berneving
	Copyright 2018

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in 
	the Software without restriction, including without limitation the rights to use,
	copy, modify, merge, publish, distribute, sublicense,and/or sell copies of
	the Software, and to permit persons to whom the Software is furnished to 
	do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included i
	n all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR 
	A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
	COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 
	OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/



// pin definitions and macros for the display interface
#define PIN_CLK		PB1
#define PIN_DATA	PB2
#define PIN_STB		PB3

#define PIN_HIGH(x)	PORTB |= (1 << (x))
#define PIN_LOW(x)	PORTB &= ~(1 << (x))

// variables for keeping track of which state we are in
#define STATE_NONE	0
#define STATE_STRING	1
#define STATE_SINGLE	2
#define STATE_INTEGER	3

char current_state = STATE_NONE;
char serial_index = 0;
char integer_length = 8;
char auto_refresh = 1;

// table of characters for the display 0-9, A-Z
unsigned char digits[] = {
	0b01111110, // zero
	0b00011000, // one
	0b00110111, // two
	0b00111101, // three
	0b01011001, // four
	0b01101101, // five
	0b01101111, // six
	0b00111000, // seven
	0b01111111, // eight
	0b01111001, // nine
	0b00000000, // empty
};

// stores the character in each place in the display
#define DISPLAY_DIGITS 8
unsigned char display_content[DISPLAY_DIGITS] =  {10, 10, 10, 10, 10, 10, 10, 10};

void setup() {
	// initialize serial communication
	Serial.begin(115200);
 
	// set display pins as output
	DDRB |= (1 << PIN_CLK) | (1 << PIN_DATA) | (1 << PIN_STB);
	
	// initial states
	PIN_LOW(PIN_CLK);
	PIN_LOW(PIN_DATA);
	PIN_LOW(PIN_STB);
	
	# show all segments at startup
	setDisplayContent(88888888, 0, 8);
	refreshDisplayContent();

	delay(2000);
	clearDispForce();  
}

void loop() {
	// do serial communication
	while(Serial.available() > 0){
		// read a character
		char data = Serial.read();

		// take action based on current state
		switch(current_state){
		case STATE_NONE:
			if(data >= 'A' && data <= 'H'){ // Single Edit Command
				current_state = STATE_SINGLE;
				serial_index = data - 'A';	
								
			} else if (data == 'S'){ // is this a "String" command?
				// yes, move to correct state and reset the index counter
				current_state = STATE_STRING;
				serial_index = 0;
				
			} else if (data == 'I'){ // "Integer" command
				current_state = STATE_INTEGER;
				
			} else if (data == 'R'){ // "Force Refresh" command
				refreshDisplayContent();
				
			} else if (data == 'L'){ // set integer length command
				integer_length = Serial.parseInt();
				
			} else if (data == 'W'){ // disable auto refresh command
				auto_refresh = 0;
				
			} else if (data == 'Q'){ // enable auto refresh command
				auto_refresh = 1;
				
			} 
			break;
			
		case STATE_STRING:
			// read input digit
			data = data - '0';
			if(data < 0 || data > 9) data = 10;
			display_content[serial_index] = (unsigned char)data;
			
			// increment index (move to next position) (this could all be done with pointers...)
			serial_index++;
			
			// if we are done filling all positions, apply change
			if(serial_index >= DISPLAY_DIGITS){
				current_state = STATE_NONE;
				
				// update display
				if(auto_refresh == 1)
					refreshDisplayContent(); 
			}
			break;
			
		case STATE_SINGLE:
			// read input digit
			data = data - '0';
			if(data < 0 || data > 9) data = 10;
			display_content[serial_index] = (unsigned char)data;
			
			// no longer in "single" mode
			current_state = STATE_NONE;
			
			// apply changes
			if(auto_refresh == 1)
				refreshDisplayContent(); 
			break;
			
		case STATE_INTEGER:
			if(data >= 'A' && data <= 'H'){ 
				// this is where to start the integer (with leading zeros and configurable length)
				serial_index = data - 'A';	
				
				
				long val = Serial.parseInt();
				
				setDisplayContent(val, serial_index, integer_length);
				if(auto_refresh == 1)
					refreshDisplayContent(); 
			}else
				current_state = STATE_NONE;			
		
			break;
		}    
	}
}

// sets the display content as a number starting at index start_index with the specified length. Leading zeros are inserted automatically
void setDisplayContent(long number, char start_index, char length){
	for(int i = start_index + length - 1; i >= start_index; i--){
		
		// skip wrong indexes
		if(i > 7 || i < 0)
			continue;
		
		display_content[i] = number % 10;
		number /= 10;
	}
}

// writes the characters of display_contents to the display
void refreshDisplayContent(){
	//check if any of the characters is a space...
	char hasSpace = 0;
	for(int i = 0; i < 8; i++){
		if(display_content[i] == 10){
			hasSpace = 1;
		break;
		}
	}

	// if the contents contain a space, enable strobe line during the shift out (does not work properly otherwise)
	if(hasSpace){
		PIN_HIGH(PIN_STB);
	}

	// write the eight digits
	shiftDisplayContent();

	// strobe data
	strobeDisp();
}

void shiftDisplayContent(){
	for(int i = DISPLAY_DIGITS - 1; i >= 0; i--){
		shift_out_char7(digits[display_content[i]], i == 0);
	}
}

void strobeDisp(){
	PIN_HIGH(PIN_STB);
	PIN_LOW(PIN_STB);
}

// fill display with zeroes (note: this happens continuously on the display since STB must be active for writing all zeroes unfortunately...)
void clearDispForce(){
	PIN_LOW(PIN_DATA);
	PIN_HIGH(PIN_STB);
	for(int i = 0; i < 8*DISPLAY_DIGITS; i++){
		PIN_HIGH(PIN_CLK);   
		PIN_LOW(PIN_CLK);
	}
	PIN_LOW(PIN_STB);
}


/*
	Shift the lower 7 bits of the provided character to the display
*/
void shift_out_char7(unsigned char data, bool last){
	for(unsigned char i = 0; i < (last ? 7 : 8); i++){

		// write data
		if(data & 0x01) // lsb bit == 1 ?
			PIN_HIGH(PIN_DATA);
		else
			PIN_LOW(PIN_DATA);
		
		// pulse clock
		PIN_HIGH(PIN_CLK);
		PIN_LOW(PIN_CLK);
			
		// shift data
		data = data >> 1;
	}
}