Rapid prototyping for music, art and design work

The UnExpressiveBrush

The UnExpressiveBrush was built to test the capabilities of the ultrasound and acceleration sensors by Parallax. The intention was to build a system where one could simulate painting with a very wet brush. With it one can paint by sprinkling with vigorous brush movements in front of a canvas. However, we cut a few corners and ended up with a system that was significantly less usable than the original plan. First of all, we did not build a paint sprinkling simulation. Instead we used GIMP and its ready-made brushes for the painting. We also did not use the acceleration information from the brush for anything else except sending a mouse down event whenever a certain threshold was exceeded and a mouse-up when acceleration returned to lower values. As a result we had the ability to sprinkle paint with high acceleration movements along one axis and to spread paint by tilting the brush to one direction. The same movements were used to select colors from the palettes available in GIMP. The laptop keyboard was needed for switching windows.

The UnExpressiveBrush from Poika Isokoski and Harri Rantala.


The system consisted of three pieces of software. One worked in BS2 to get the x and y coordinates from the ultrasound sensors and send that together with the acceleration data to a PC through a serial connection. The second part of software received this data and translated it into mouse events that were sent to Windows to deliver to whichever application happened to own the screenspace. The third piece of software was GIMP that received mouse movement and button up and button down events just like it normally does.

Below you can find two potentially useful snippets from the code. The first is the C++ class that we used to read the serial port in Windows XP. The second is the code that sent the mouse events. Putting the whole Visual C++ project online would not be much more helpful that these snippets since most people will not be using Visual C++ anyway. Finally as the last item below you can find the BS2 code that we used to read the sensors.

First the serial connection class:


class BS2Connection

	char ch;
	char line[1024];
	unsigned long nread;

	char getchar(int i){
		return line[i];
	void getLine(char* str){
	// returns 0 on success !=0 on error
	int openPort(LPCSTR portname){
		if(hCom!=INVALID_HANDLE_VALUE) closePort();

		DCB dcb;
		BOOL fSuccess;

		hCom = CreateFile( portname,
                     0,    // comm devices must be opened w/exclusive-access
                     NULL, // no security attributes
                     OPEN_EXISTING, // comm devices must use OPEN_EXISTING
                     0,    // not overlapped I/O
                     NULL);  // hTemplate must be NULL for comm devices

		if (hCom == INVALID_HANDLE_VALUE) { // Handle the error.
			printf ("CreateFile failed with error %d.\n", GetLastError());
			return 1;

		// We will build on the current configuration, and skip setting the size
		// of the input and output buffers with SetupComm.

		fSuccess = GetCommState(hCom, &dcb);

		if (!fSuccess) { // Handle the error.
			printf ("GetCommState failed with error %d.\n", GetLastError());
			return 2;

		// Fill in the DCB: baud=57,600 bps, 8 data bits, no parity, and 1 stop bit.
		dcb.BaudRate = CBR_9600;     // set the baud rate
		dcb.ByteSize = 8;             // data size, xmit, and rcv
		dcb.Parity = NOPARITY;        // no parity bit
		dcb.StopBits = ONESTOPBIT;    // one stop bit

		fSuccess = SetCommState(hCom, &dcb);

		if (!fSuccess) { // Handle the error.
			printf ("SetCommState failed with error %d.\n", GetLastError());
			return 3;
		return 0;

	int closePort(){
		return 0;

	int next(){
		return ReadFile(hCom,&ch,1,&nread,NULL);

	int nextLine(){
		int status=0;
		int i=0;
		while(ch!='\r' && i<1023){
			if(status==0) status;
		return status;

This is the main program that uses the class above to read the data from the serial port and sends mouse events appropriately.


void sendMouseEvent(int x, int y, int click){
	if (click>0){
	if (click<0){

int _tmain(int argc, _TCHAR* argv[])
	float oldx,oldy=0;
	BS2Connection c;
	char line[1024];
		int result=c.nextLine();
			DWORD error=GetLastError();
			std::cout << "error: " << error << std::endl;
		} else{
			std::cout << "\"" <<line <<"\""<> y >> x >> click;
			float xmin=1100;
			float ymin=3200;
			float xmax=3300;
			float ymax=4200;
			if(xxmax) x=oldx;
			if(yymax) y=oldy;
			else click=0;
	return 0;

Finally the BS2 Basic code:

' 2009 & Harri Rantala (UCIT DIE09)
' Using PING))) Ultrasonic sensor and Accelerometer
' measures distances in 2D space and X axis from an accelerometer

' ---[ I/O Definitions ]---
pX    PIN  15              
pY    PIN  0               
gravX PIN  2               

' ---[ Variables ]---
wTime       VAR  Word
ping         VAR  Byte

wXPulse         VAR     WORD            ' raw data from accelerometer
wXGravity       VAR     WORD            ' X axis gravity             

' ---[ Constants ]---
cPulseState     CON     1
cScale          CON     $200                    ' 2.0 us per unit

' ---[ Constants ]---
cTrigger     CON     5    ' trigger pulse = 10 uS for BS2

' ---[ Initialization ]---
'DEBUG CLS, "2D distances plus X axis", CR

' ---[ Main Code ]---

    GOSUB Read_Accelerometer

    ping = 0
    GOSUB ReadPing
    DEBUG DEC5 wTime, " "

    ping = 15
    GOSUB ReadPing
    DEBUG DEC5 wTime, " "

    DEBUG SDEC5 wXGravity, CR


' ---[ Subroutines ]---
  ' PULSOUT Pin, Duration
  ' pin: Pin number (0-15)
  ' Duration: specifies the duration of the pulse. (BS2: a unit = 2us)
  PULSOUT ping, cTrigger
  ' PULSIN Pin, State, Variable
  ' Pin: Pin number (0 - 15)
  ' State: Specifies whether the puse to be mesured is low(0) or high(1)
  ' Variable: the measured pulse duration will be stored.
   PULSIN ping, 1, wTime

        ' PULSIN Pin, State, Variable
        ' Pin: Pin number (0 - 15)
        ' State: Specifies whether the puse to be mesured is low(0) or high(1)
        ' Variable: the measured pulse duration will be stored.
        PULSIN gravX, cPulseState, wXPulse                      ' read pulse 
        wXPulse = wXPulse */ cScale                             ' convert to 
uSecs for BS2
        wXGravity = ((wXPulse / 10) - 500) * 8          ' calculating the 
pulse to 1/1000 g

'       PULSIN pYin, cPulseState, wYPulse
'       wYPulse = wYPulse */ cScale
'       wYGravity = ((wYPulse / 10) - 500) * 8
Facebook Twitter Email

Category: Graduate School in User-Centered Information Technology, Student Projects


Comments are closed.

Social links powered by Ecreative Internet Marketing