Four months ago I decided I wanted to build a tape reader. At first I thought it would read and write data, but writing was very problematic. I didn't have a marker so big, and the writing was very inaccurate. I made a prototype but I couldn't program it well. I stopped working on this until a month ago or so when I knew how to write a good program. As soon as I had some time I started building.

The robot is made of a few structural parts. Two motors at both sides are connected with two rolls with a clutch. A clutch (axes connector) makes it easy to connect and disconnect motors from rolls. Rolls are made from standard Lego rims which are covered with rubber to provide more friction. There are two cardboard discs on each roll that are leading the tape. Tape is scrolled under the color sensor which is reading data.

Now, about the tape. It's a strap of paper with black rectangles printed on it. The first four bits form the initialization section: BLACK | WHITE | BLACK | WHITE. The robot uses this section to determine the length of one bit on the tape. After the initialization section there is the data section. It can be as long as you wish. One important thing is that at the end of the data section, the last bit must be BLACK. If it is in your data then you need not to worry. Otherwise you should add this one black bit at the end. This is not a problem since the tape's length is not limited.

The most important part: the algorithm. The whole reading algorithm is based on transitions from BLACK to WHITE and from WHITE to BLACK. That's why the track needs to end with a black bit. If it weren't there, the program would continue to read white bits forever, without appending them to the output bit array. The program analyzes readings from the light sensor and when it detects a transition, it appends as many bits (one colour bits) to the result array. I used hysteresis to filter the noise from the sensor.

At first the program measures length of white and black bits. If the difference between these lengths is substantial, the program reports an Init error (interference from the external sources of light, wrong initialization section, bits are not equal... ). If everything is OK, then the avarage bit length, transition levels and other parameters are saved in the memory. Then tre robot is ready to read data from the tape. It measures the distance between two transitions, divides it by one bit's length and then it knows how many one colour bits were read.

Example: one bit is 40 wide. It was computed during initialization. Robot detects a transition from WHITE to BLACK when the motor is turned by 134 degrees from its start position, and a transition from BLACK to WHITE with the angle equal 218. The difference is 84. 84 divided by 40 equals 2.1. This value is rounded to the nearest natural number 2. In this way the program knows that 2 BLACK bits were read. Simple, isn't it?

Reading is extremely accurate because only relative lengths are being measured. The tape can be scrolled with great speed without loosing accuracy.

I included some functions to write all the data to text files in the brick's memory to make it easier to debug later. Example code beneath reads text and displays it on the screen. Every letter is a 7 bit number from the ASCII code.

The building instruction is available in the video section here and in Lego Digital Designer file here. The link to LDD is at the bottom of this page.

The whole project was a great success. The robot never made a single mistake while reading. Every time I got what I encoded. At first I was testing it with motors at 10% speed. I was really surprised when after increasing the speed to 60% the reading was still accurate without any changes in the code. I strongly reccomend building it for everyone.



3D Gallery

To watch pictures in this gallery you need special red-cyan anaglyph glasses. They are to buy in many Internet stores for less than 1$.

Program code

//author: Krzysztof Kapusta, All rights reserved #define LEFT_MOTOR OUT_B #define RIGHT_MOTOR OUT_C #define SENSOR S2 #define READ SensorRaw(SENSOR) #define ROT_LEFT MotorRotationCount(LEFT_MOTOR) #define ROT_RIGHT MotorRotationCount(RIGHT_MOTOR); #define MAX_SPEED 40 // increase if you want #define HIS_WIDTH_PRC 10.0 int MED; int HIS_RISING_LEVEL; int HIS_FALLING_LEVEL; int ONE_BIT_WIDTH; void init() { SetSensorType(SENSOR, SENSOR_TYPE_COLORRED); SetSensorMode(SENSOR, SENSOR_MODE_RAW); ResetSensor(SENSOR); Wait(MS_100); PosRegSetMax(LEFT_MOTOR, MAX_SPEED, 100); PosRegSetMax(RIGHT_MOTOR, MAX_SPEED, 100); Float(LEFT_MOTOR); Float(RIGHT_MOTOR); } bool initReading() { int min = 1024, max = 0, read; long start_pt, mid_pt, end_pt; int white = READ; Float(LEFT_MOTOR); OnFwdReg(RIGHT_MOTOR, -MAX_SPEED, OUT_REGMODE_SPEED); while(1) { read = READ; if (read < white-50) break; if (read > max) max = read; } // first falling edge while (1) { read = READ; if (read > min + 20) break; if (read < min) min = read; } // just before first rising edge MED = (max+min)/2; MED *= 1.05; // !!! worked best for me, modify if you constantly get Initialization Error HIS_RISING_LEVEL = MED + HIS_WIDTH_PRC/100.0*(max-MED); HIS_FALLING_LEVEL = MED - HIS_WIDTH_PRC/100.0*(MED-min); while (READ < HIS_RISING_LEVEL); // while bit is black start_pt = ROT_RIGHT; while (READ > HIS_FALLING_LEVEL); // while bit is white mid_pt = ROT_RIGHT; while (READ < HIS_RISING_LEVEL); // while bit is black Off(RIGHT_MOTOR); end_pt = ROT_RIGHT; ONE_BIT_WIDTH = abs(start_pt - end_pt)/2; if (abs(start_pt - mid_pt) - abs(mid_pt - end_pt) > 0.2 * ONE_BIT_WIDTH) return false; else return true; } #define MAX_READ_LENGTH 1000 // number of bits you suppose to read, there's no protection for writing more #define FILES_LENGTH 5000 // increase if files are incomplete mutex READING_MUTEX; bool STOP_READING = false; bool DATA[MAX_READ_LENGTH]; // data read, data[0] is always false, data[1]... is your data long DATA_POS = 0; task read() { byte bits_h, data_h; DeleteFile("bits.txt"); CreateFile("bits.txt", FILES_LENGTH, bits_h); DeleteFile("data.txt"); CreateFile("data.txt", FILES_LENGTH, data_h); bool prev_bit = true; long count, start_count = ROT_RIGHT; int val, bits, cnt; float fbits, delta; string tmp; bool stop_flag = false; Float(LEFT_MOTOR); OnFwdReg(RIGHT_MOTOR, -MAX_SPEED, OUT_REGMODE_SPEED); while (true) { if (STOP_READING) { // one more pass with instructions in 'if' Off(RIGHT_MOTOR); stop_flag = true; } val = READ; if ((prev_bit && val < HIS_FALLING_LEVEL) || (!prev_bit && val > HIS_RISING_LEVEL) || stop_flag) { count = ROT_RIGHT; fbits = 1.0*abs(start_count - count)/ONE_BIT_WIDTH; bits = fbits + 0.5; // integer cast tmp = FormatNum("%f", fbits); WriteLnString(bits_h, tmp, cnt); delta = fbits - bits; if (delta > 0.3) ONE_BIT_WIDTH++; else if (delta < -0.3) ONE_BIT_WIDTH--; Acquire(READING_MUTEX); for (long i = DATA_POS; i < DATA_POS+bits; i++) { DATA[i] = !prev_bit; tmp = FormatNum("%d", !prev_bit); WriteString(data_h, tmp, cnt); } DATA_POS += bits; Release(READING_MUTEX); prev_bit = !prev_bit; start_count = count; } if (stop_flag) break; } CloseFile(bits_h); CloseFile(data_h); } bool STOP_PRINTING = false; task printCharacters() { long prev_data_pos = 1; int print_x = 0, print_y = LCD_LINE1; char character; while (!STOP_PRINTING) { if (DATA_POS >= prev_data_pos + 7) { character = 0; for (long i = prev_data_pos; i < prev_data_pos+7; i++) { character <<= 1; character += DATA[i]; } if (character == 10) { // new line print_y += (LCD_LINE2-LCD_LINE1); print_x = 0; } else if (character == 3 || character == 4) // end of tape STOP_PRINTING = true; else { TextOut(print_x, print_y, FormatNum("%c", character)); print_x += 6; if (print_x > 93) { print_y += (LCD_LINE2-LCD_LINE1); print_x = 0; } } prev_data_pos += 7; } Wait(MS_100); } } task main() { init(); if (!initReading()) { TextOut(0, LCD_LINE4, "Init error"); while(1); } StartTask(read); StartTask(printCharacters); while(!STOP_PRINTING); STOP_READING = true; while(!ButtonPressed(BTNCENTER, false)); }

Show/hide all the code


Aha, to jak znajdę czas to się w to pobawię. Napotkałem też teraz jeszcze jeden problem - lewy silnik, podpięty do portu B mi nie działa. Jest on w pełni sprawny, a tu odmawia posłuszeństwa. Prawy działa dobrze, ale kręci się w odwrotnym kierunku w stosunku do filmu - również nie wiem dlaczego. Może to wina wgranego softu? - w stosunku do innego w tym dźwięk nie działa, i może ma też jakieś błędy, które pokazują się w tym programie. Jeśli wiedziałbyś jak i co poprawić, żeby ten silnik działał to proszę o pomoc.

Posted by: Anonymous | 2014-08-31

Zamiana bitów z taśmy na muzykę wymaga wymyślenia jakiegoś sposobu kodowania nut muzycznych. W moim programie każdy dźwięk miał zapisaną informację o jego wysokości (częstotliwości) i czasie trwania. Musisz sam wymyślić kod który najlepiej będzie tobie pasował i poeksperymentować z nim. Mi to zajęło całkiem sporo czasu :)

Posted by: admin | 2014-08-31

Witam Mam pytanie - co powinienem zrobic, zeby zamienic papierowy kod na muzyke?. Kiedy testowalem robota, mialem do odczytu tylko tekst.

Posted by: slawus1998 | 2014-08-31

Upload new firmware using instructions from BricxCC website.

Posted by: Admin | 2014-02-06

ok thank you :) but it says error in line 25 and 26 in the code when i want to download to the NXT what can i do.??

Posted by: Domi | 2014-02-06

See all comments

Post your comment