Projektrapport - RoboWalter Projektarbete i kursen Digitala System (EITA15) vid Lunds Tekniska Högskola (LTH) Grupp 1: Angelika Larsson, Oscar Persson, Albin Andersson och Fanny Zolotarevskaia Handledare: Bertil Lindvall och Lars-Göran Larsson Datum: 2019-05-16
Sammanfattning Målet med projektet var att bygga en fungerande prototyp genom att konstruera hårdvara samt mjukvara som uppfyller kraven i kravspecifikationen. Prototypen (även kallad RoboWalter) framställdes med hjälp av utvalda komponenter samt Atmel Studio 7. Den slutliga versionen av prototypen kan styras genom Bluetooth och visa sina känslor på en display. Nyckelord Digitala system, Prototyp, Utveckling, Bluetooth Abstract The purpose of this project is to build a working prototype by both constructing hardware and software that fulfil the requirements in the requirements specification. The prototype (also called RoboWalter) was constructed with selected components as well as Atmel Studio 7. The final product can be controlled via Bluetooth and shows his feelings on the display. Keywords Digital systems, Mockup, Development, Bluetooth 1
Innehållsförteckning 1. Inledning 3 1.1 Bakgrund 3 1.2 Syfte 3 1.3 Målformulering 3 1.4 Kravspecifikation 3 1.5 Avgränsningar 3 2. Teknisk bakgrund 4 2.1 Hårdvara 4 2.2 Mjukvara 5 3. Metod 6 3.1 Planering 6 3.2 Konstruktion av hårdvara 6 3.3 Programmering av mjukvara 6 4. Resultat 7 5. Diskussion 8 6. Källförteckning 9 Appendix 10 A. Tidsplan 10 B. Kopplingsschema 10 C. Översikt över kontrollenhetens knappar som styr prototypen 11 D. Källkod 11 2
1. Inledning 1.1 Bakgrund I kursen EITA15 - Digitala system ska studenter i grupp om fyra personer konstruera en fungerande prototyp. Vilket sker genom att göra ett kopplingsschema, koppling av komponenter samt utveckling av mjukvara. Vilken typ av prototyp som ska tas fram väljs av studenterna i samråd med handledare. 1.2 Syfte Avsikten med arbetet är att konstruera en fungerande prototyp och utveckla problemlösningsförmågan, samt att kunskaper från tidigare kurser ska kunna implementeras och användas. Det förväntade resultatet är en prototyp som ska kunna styras via Bluetooth och röra sig i olika riktningar. 1.3 Målformulering Målet är att skapa en prototyp som ska kunna styras via Bluetooth. Den ska även ha en strömbrytare som är kopplad till en grön lysdiod som lyser när strömmen är på. I mån av tid ska den ha en display med olika ansiktsuttryck beroende på hastigheten han färdas i. 1.4 Kravspecifikation 1. RoboWalter ska kunna styras genom Bluetooth. 2. RoboWalter har en strömbrytare som är kopplat till en grön lysdiod som lyser när strömmen är på. 3. RoboWalter ska i mån av tid ha en display med olika ansiktsuttryck beroende på hastigheten han färdas i. 1.5 Avgränsningar Arbetet är att få en fungerande prototyp som uppfyller kraven enligt kravspecifikationen. Därför skedde ingen design på prototypen eftersom det inte ingick i kraven. 3
2. Teknisk bakgrund Nedan följer en beskrivning av komponenterna som prototypen är uppbyggd av. Hur dessa är kopplade till varandra finns i Appendix A. 2.1 Hårdvara Processor : AVR ATmega 1284 som är 8-bitars processor. Processorn har fyra portar sorterade i PA-D och har totalt 40 pinnar. Processorn programmeras för att styra övriga komponenter. Resistorer : Tre vanliga resistorer på 1 kω, 1.2 kω och 2.2 kω används för att justera spänningen till Bluetooth-modulen och lysdioden. Chassi : Pololu Romi Chassis Kit med 2 DC-motorer. Lysdiod: En grön lysdiod som används för att se när RoboWalter har ström. Strömbrytare: En standard strömbrytare som kan bryta strömmen. H-brygga: DRV8833 används för att driva motorerna i två riktningar och i en programmerbar hastighet. Bluetooth-modul: HC-06 används för kommunikation mellan RoboWalter och datorn. Xbox-kontroll: En Xbox360-kontroll används för att sända signaler. Trådlös spelmottagare: Används för kommunikation mellan Xbox-kontrollen och datorn. JTAG : Atmel-ICE används för att överföra koden från datorn till processorn. Display: μoled-128g2 används för att visa RoboWalters känslor. 4
2.2 Mjukvara Programmering: Atmel Studio 7 används för att programmera prototypen. Koden för prototypen är skriven i programspråket C. Källkoden finns i Appendix C. Prototypen fungerar på följande sätt: Vid påslagning initialiseras prototypens register, variabler och skärmen. Eftersom RoboWalter använder avbrott exekveras inget förrän data har tagits emot av Bluetoothmodulen. Då datan har anlänt till processorn behandlas datan för att utföra det valda kommandot. AntiMicro : Används för att kartlägga de olika knapparna på Xbox-kontrollen för att kunna ge knapparna motsvarande valda kommando. TeraTerm : Används för att skicka data till och från en Bluetoothenhet. 5
3. Metod 3.1 Planering Arbetet var upplagt enligt tidsplanen i Appendix A och börjades med att skapa en kravspecifikation. Kravspecifikationen skapades för att ha tydliga mål att fokusera på. Tidsplanen gjordes för att lättare bli färdig i tid men även för att vara medvetna om vad som skulle göras under mötena. Komponenterna till prototypen letades upp som behövdes för att uppfylla kraven och börja bygga prototypen. Vidare studerades datablad för de olika komponenterna för att kunna rita upp ett kopplingsschema (se Appendix B ). Kopplingsschemat uppdaterades några gånger under arbetets gång då fel upptäcktes. 3.2 Konstruktion av hårdvara Efter att kopplingsschemat blivit godkänt av handledaren påbörjades konstruktionen av prototypen. Först löddes sex stycken pinnar och två stycken ingångar för strömförsörjning fast på mönsterkortet. Därefter placerades alla komponenter ut på mönsterkortet för att sedan kopplas ihop enligt kopplingsschemat (se Appendix B ). Kopplingen mellan komponenterna skedde genom att trådar virades mellan komponenternas ben med hjälp av en så kallad virpistol. Mönsterkortet med alla komponenter monterades på chassit med distanser och muttrar. 3.3 Programmering av mjukvara Samtidigt som prototypen konstruerades så kodades mjukvaran i Atmel Studio 7 som möjliggjorde styrningen av prototypen. Vid programmeringen av programkoden användes kunskaper från laborationerna tidigare i kursen och även ny kunskap från databladen. Atmel Studio 7 användes för att det var den enda utvecklingsmiljön som var bekant. 6
4. Resultat Resultatet blev en prototyp som kan styras med en Xbox-kontroll genom Bluetooth. Styrningen av prototypen sker med hjälp av olika hastigheter på motorerna. Prototypen har också en strömbrytare som är kopplad till en grön lysdiod som lyser då strömmen är på. Det finns även en display på prototypen som visar glatt ansikte när den kört snabbt och ledset ansikte när den kör sakta och står still. Därmed är alla krav uppfyllda. 7
5. Diskussion RoboWalter projektet har gått smidigt de flesta dagarna då projektet har arbetats med. Projektet påbörjades tidigt eftersom en klar idée av hur prototypen skulle fungera fanns redan färdig vid start. Projektet har varit lärorikt då de flesta sakerna varit nya och information inte varit lättillgänglig. Vilket gjorde att mycket tid lades på att lära sig hur de olika komponenter fungerar. Ett problem som uppkom när koden för Bluetoothmodulen skulle skrivas var att den inbyggda klockan på processorn hade en annan frekvens än vad som antogs. Vilket gjorde att fel information skickades och därför kunde inte informationen behandlas. Problemet löstes genom att ändra en rad i programkoden som berättar för processorn vilken frekvens som klockan har. Displayen visade sig också vara ett problem då koden för den skulle skrivas. En laboration som hade genomförts tidigare i kursen innehöll givande information och programkod om hur displayen skulle programmeras. Denna information användes flitigt men var inte komplett. Till slut hittades databladet för processorn som finns i displayen, vilket innehöll kommandona som behövdes för att skriva färdigt koden. Resultatet av denna delen av koden är att prototypen kan visa känslor genom ansiktsuttryck på sin display. RoboWalter styrs bara indirekt med en Xbox-kontroll. I verkligheten kommunicerar Xbox-kontrollen via IR med en dator och datorn kommunicerar via Bluetooth med Bluetooth-modulen på RoboWalter. De signaler som skickas motsvarar tangenter på ett tangentbord. Så egentligen är Xbox-kontrollen bara en förlängning av tangentbordet. Förbättringar som skulle kunna göras är till exempel att prototypen ska kunna accelerera/retardera till en given hastighet istället för att ge given hastighet direkt. Det skulle ge en mjukare start samt mjukare hastighetsförändringar. Detta skulle i sin tur minska slitage på komponenterna och chassit då risken för skakningar minskar. 8
6. Källförteckning [1] ATmega1284 Datasheet, May 2016. [Online] Available: http://ww1.microchip.com/downloads/en/devicedoc/atmel-42718-atmega1284_datasheet.pdf [2] DRV8833 Dual H-Bridge Motor Driver Datasheet, July 2015. [Online]. Available: http://www.ti.com/lit/ds/symlink/drv8833.pdf [3] HC-06 Bluetooth Module Datasheet REV: 2.0, 6 September 2006 [Online]. Available: http://www.sgbotic.com/products/datasheets/wireless/hc06_datasheet.pdf [4] 1.5 microoled GOLDELOX Display µoled-128-g2 Datasheet, 5 September 2012. [Online]. Available: https://old.4dsystems.com.au/downloads/microoled/uoled-128-g2/docs/uoled-128-g2-d atasheet-rev1.pdf [5] Goldelox Processor Serial commands reference manual, 27 July 2017 [Online]. Available: https://www.4dsystems.com.au/productpages/goldelox/downloads/goldelox_serialcmd manual_r_2_0.pdf 9
Appendix A. Tidsplan B. Kopplingsschema 10
C. Översikt över kontrollenhetens knappar som styr prototypen D. Källkod #define F_CPU 1000000UL #define BAUDE_RATE 9600 #include <avr/interrupt.h> #include <avr/io.h> #include <util/delay.h> void usartinit(); char usartreceive(uint8_t channel); void usarttransmit(uint8_t channel, unsigned char data); void timerinit(); void setpulselefta(uint8_t pulse); void setpulseleftb(uint8_t pulse); void setpulserighta(uint8_t pulse); void setpulserightb(uint8_t pulse); 11
void leftforward(uint8_t speed); void leftbackwards(uint8_t speed); void rightforward(uint8_t speed); void rightbackwards(uint8_t speed); void oledinit(uint8_t size); void sendstring(unsigned char *, int); void sendchr(unsigned char); void newline(); void newempty(); void drawcircle(int16_t x, int16_t y, int16_t radius, int16_t color, int16_t filled); void drawline(int8_t x1, int8_t y1, int8_t x2, int8_t y2, int16_t color); void sendsmiley(int x); void drawmouth(double happines); void init(); volatile uint8_t speedoffset = 0; volatile uint8_t currentleftspeed = 0; volatile uint8_t lastleftspeed = 0; volatile uint8_t currentrightspeed = 0; volatile uint8_t lastrightspeed = 0; int main(void){ _delay_ms(4000); init(); unsigned char stop[] = {0x00, 0x0C, 0x00, 0x00; sendstring(stop, sizeof(stop)); //PORT(A-D) write //PIN(A-D) read //OC3B = INB2 //OC3A = INB1 drawface(10.0); while (1){ 12
void drawface(double happiness){ drawcircle(64, 62, 60, 0xE1A0, 0); //_delay_ms(50); drawcircle(64, 64, 60, 0xEED2, 1); _delay_ms(100); drawcircle(35, 40, 10, 0xFFFF, 1); drawcircle(89, 40, 10, 0xFFFF, 1); //_delay_ms(50); unsigned char nose[] = {0x00, 0x05, 0, 8, 0x00, 58, 0x00, 58, 0x00, 54, 0x00, 58, 0x00, 70, 0x00, 74, 0x00, 70, 0x00, 70, 0x00, 40, 0x00, 55, 0x00, 65, 0x00, 70, 0x00, 70, 0x00, 65, 0x00, 55, 0x00, 40, 0x00, 0x00; sendstring(nose, sizeof(nose)); if (happiness < 0){ drawline(25, 32, 48, 25, 0xE1A0); drawline(103, 32, 80, 25, 0xE1A0); drawcircle(35, 40, 3, 0x0000, 1); drawcircle(89, 40, 3, 0x0000, 1); else{ drawline(25, 26, 48, 25, 0xE1A0); drawline(103, 26, 80, 25, 0xE1A0); drawcircle(35, 40, 3, 0x2B1D, 1); drawcircle(89, 40, 3, 0x2B1D, 1); //_delay_ms(100); drawmouth(happiness); void drawmouth(double happines){ 13
unsigned char init[] = {0x00, 0x05, 0, 10; sendstring(init, sizeof(init)); for (int x = 0; x < 10; x++){ sendchr(0x00); uint8_t f = (uint8_t)(30 + (68 / 9 * x)); sendchr(f); for(int y = -5; y <= 5; y++){ if (y!= 0){ sendchr(0x00); uint8_t g = 100 - (y * y / happines); sendchr(g); sendchr(0x00); sendchr(0x00); /** * Initialize all variables, methods. */ void init(){ cli(); usartinit(); timerinit(); //set input for bluetooth module input(tx) (RXD1 on atmega) DDRD &= ~(1 << 2); //Enable SLP h-brygga DDRA = (1 << DDRA5); PORTA = (1 << DDRA5); oledinit(3); //Set first bit in the status register (SREG) sei(); leftforward(0); 14
rightforward(0); /** ##### BLUETOOTH MODULE INTERRUPTS ##### * This part will run whenever we receive something * from the bluetooth module. */ ISR(USART1_RX_vect){ volatile unsigned char data = UDR1; volatile uint8_t speed = 140; lastleftspeed = currentleftspeed; lastrightspeed = currentrightspeed; usarttransmit(1, data); switch(data){ //a leftforward case 193: leftforward(speed); currentleftspeed = speed; //b left backwards case 194: leftbackwards(80); currentleftspeed = 80; //c right forward case 195: rightforward(speed + speedoffset); currentrightspeed = speed + speedoffset; //ENTER right backwards case 29: rightbackwards(80); currentrightspeed = 80; //, right stop case 92: 15
leftforward(0); currentleftspeed = 0; //. left stop case 94: rightforward(0); currentrightspeed = 0; //8 both stop case 112: case 120: rightbackwards(0); leftbackwards(0); currentrightspeed = 0; currentleftspeed = 0; //9 add to speedoffset case 121: case 113: speedoffset += 2; //I add to speedoffset case 217: case 209: speedoffset -= 2; //J left fast forward case 218: case 210: leftforward(speed * 1.6); currentleftspeed = speed * 1.6; //K left slow forward case 219: case 211: leftforward(speed * 0.4); currentleftspeed = speed * 0.4; //L right fast forward 16
case 220: rightforward(speed * 1.6); currentrightspeed = speed * 1.6; //M right slow forward case 221: rightforward(speed * 0.4); currentrightspeed = speed * 0.4; case 102: rightforward(255); leftforward(255); currentrightspeed = 250; currentleftspeed = 250; uint8_t max = (currentleftspeed > currentrightspeed)? currentleftspeed : currentrightspeed; max = (max > 200)? 180 : max; if(max - 79!= 0){ if ((currentleftspeed!= lastleftspeed) (currentrightspeed!= lastrightspeed)){ drawface(100 / (max - 80)); //################ OLED void oledinit(uint8_t size){ //unsigned char clear[] = {0xff, 0xd7; unsigned char text_width[] = {0xff, 0x7c, 0x00, size; unsigned char text_height[] = {0xff, 0x7b, 0x00, size; unsigned char move[] = {0xff, 0xe4, 0x00, 0x00, 0x00, 0x00; 17
//send_string(clear, sizeof(clear)); sendstring(text_width, sizeof(text_width)); sendstring(text_height, sizeof(text_height)); sendstring(move, sizeof(move)); inline void sendstring(unsigned char *msg, int length){ for (int i = 0; i < length; i++) { sendchr(msg[i]); void sendchr(unsigned char c) { while (!(UCSR0A & (1 << UDRE0))); UDR0 = c; void newline(){ unsigned char move[] = {0xff, 0xe4, 0x00, 0x01, 0x00, 0x00; sendstring(move, sizeof(move)); void newempty(){ unsigned char move[] = {0xff, 0xe4, 0x00, 0x00, 0x00, 0x01; sendstring(move, sizeof(move)); void drawcircle(int16_t x, int16_t y, int16_t radius, int16_t color, int filled){ uint8_t x1 = (uint8_t)(x % 256); uint8_t x2 = (uint8_t)(x >> 8); uint8_t y1 = (uint8_t)(y % 256); uint8_t y2 = (uint8_t)(y >> 8); uint8_t rad1 = (uint8_t)(radius % 256); uint8_t rad2 = (uint8_t)(radius >> 8); 18
uint8_t color1 = (uint8_t)(color % 256); uint8_t color2 = (uint8_t)(color >> 8); if (filled){ unsigned char circledata[] = {0xff, 0xCC, x2, x1, y2, y1, rad2, rad1, color2, color1; sendstring(circledata, sizeof(circledata)); else{ unsigned char circledata[] = {0xff, 0xCD, x2, x1, y2, y1, rad2, rad1, color2, color1; sendstring(circledata, sizeof(circledata)); void drawline(int8_t x1, int8_t y1, int8_t x2, int8_t y2, int16_t color){ uint8_t color1 = (uint8_t)(color % 256); uint8_t color2 = (uint8_t)(color >> 8); unsigned char linedata[] = {0xff, 0xD2, 0x00, x1, 0x00, y1, 0x00, x2, 0x00, y2, color2, color1; sendstring(linedata, sizeof(linedata)); //######### DRIVE METHODS ############ /** * Drives the left motor forward with a defined speed. * Uses PWM to set the defined speed. * * @param speed the desired speed of the motor **/ void leftforward(uint8_t speed){ 19
PORTB &= ~(1 << 6); setpulseleftb(0); setpulselefta(speed); /** * Drives the left motor backwards with a defined speed. * Uses PWM to set the defined speed. * * @param speed the desired speed of the motor **/ void leftbackwards(uint8_t speed){ setpulselefta(0); setpulseleftb(speed); /** * Drives the right motor forward with a defined speed. * Uses PWM to set the defined speed. * * @param speed the desired speed of the motor **/ void rightbackwards(uint8_t speed){ setpulserightb(0); setpulserighta(speed); /** * Drives the right motor backwards with a defined speed. * Uses PWM to set the defined speed. * * @param speed the desired speed of the motor **/ void rightforward(uint8_t speed){ setpulserighta(0); 20
setpulserightb(speed); //############### OLED ################# void setpixel(uint8_t x, uint8_t y){ //TODO //################### USART ###################### /** Enables USART[0-1] with default values both * the OLED display and bluetooth module */ void usartinit(){ //#### USART0 OLED DISPLAY //Nollställer värden UCSR0B= 0x00; UCSR0C = 0x00; //Baud rate UBRR0 = 6; //(9600bps) //Enable transmitter, receiver UCSR0B = (1 << RXEN0) (1 << TXEN0); //8-bits (011) UCSR0C = (1 << UCSZ00) (1 << UCSZ01); //UCSR0B &= ~(1 << UCSZ02); //1 stop bit (0) //UCSR0C &= ~(1 << USBS0); 21
//#### USART1 BLUETOOTH MODULE //Nollställer värden UCSR1B= 0x00; UCSR1C = 0x00; //9600bps, 8-bits, 1 stop bit, parity none //Baud rate 9600 UBRR1 = ((F_CPU / (BAUDE_RATE * 16UL)) - 1); //Enable transmitter, receiver UCSR1B = (1 << RXEN1) (1 << TXEN1); //Enables receiver, transmitter interrupts UCSR1B = (1 << RXCIE1); //8-bits (011) UCSR1C = (1 << UCSZ00) (1 << UCSZ01); //UCSR1B &= ~(1 << UCSZ02); //1 stop bit (0) //UCSR1C &= ~(1 << USBS1); //Pairty none (00) //UCSR1C &= ~(1 << UPM11); //UCSR1C &= ~(1 << UPM10); /** Returns the received data from USART buffert.*/ char usartreceive(uint8_t channel){ switch (channel){ case 0: //Wait as long as no data in receiver buffer while (!(UCSR0A & (1 << RXC0))); //Return data from buffer return UDR0; case 1: 22
////TODO has to be checked //Wait as long as no data in receiver buffer while (!(UCSR1A & (1 << RXC1))); //Return data from buffer return UDR1; default: return '_'; /** Sends the data to the USART channel.*/ void usarttransmit(uint8_t channel, unsigned char data){ switch (channel){ case 0: //Wait as long as there is data in buffer while(!(ucsr0a & (1 << UDRE0))); //Send data 0xC6 UDR0 = data; case 1: //Wait as long as there is data in buffer while(!(ucsr1a & (1 << UDRE1))); //Send data 0xCE UDR1 = data; //################### TIMERS FOR PWM ###################### /** Timer/ counter for pwm.*/ void timerinit(){ 23
//############## TIMER 1, 3 ########## //Control register A //Compare output mode selected with COM3A1 //#### Motor left //Clear OC3A on compare match TCCR3A = (1 << COM3A1); //Clear OC3B on compare match (1-0) TCCR3A = (1 << COM3B1); //OC3A-B is output DDRB = (1 << 6) (1 << 7); //#### Motor Right //Clear OC1A on compare match TCCR1A = (1 << COM1A1); //Clear OC1B on compare match (1-0) TCCR1A = (1 << COM1B1); //O1A-B is output DDRD = (1 << 4) (1 << 5); //Fast PWM with ICR3 (1-1-1-0) //ICR3 is the top, at this value the timer will restart TCCR3A = (1 << WGM31); TCCR3B = (1 << WGM32) (1 << WGM33); //Prescaler appropriate division factor is used (1-0-1) TCCR3B = (1 << CS31) (1 << CS30); //Fast PWM with ICR1 (1-1-1-0) //ICR3 is the top, at this value the timer will restart TCCR1A = (1 << WGM11); TCCR1B = (1 << WGM12) (1 << WGM13); 24
//Prescaler appropriate division factor is used (1-0-1) TCCR1B = (1 << CS11) (1 << CS10); //Sets the period of the cycle for both timer 3 and 1 ICR3 = 255; ICR1 = 255; /** Sets the pulse for forward speed of the left motor*/ void setpulselefta(uint8_t pulse){ //Output compare register OCR3A OCR3A = pulse; /** Sets the pulse for backwards speed of the left motor*/ void setpulseleftb(uint8_t pulse){ //Output compare register OCR3B OCR3B = pulse; /** Sets the pulse for forward speed of the right motor*/ void setpulserighta(uint8_t pulse){ //Output compare register OCR1A OCR1A = pulse; /** Sets the pulse for backwards speed of the right motor*/ void setpulserightb(uint8_t pulse){ //Output compare register OCR1B OCR1B = pulse; 25