MSP430 Boids

As posted before on Twitter I’ve been playing with Olimex’s MSP430-LED boosterpack. I’ve bought some ARM Cortex M3 modules from Olimex before that I liked. This modle is really nice hardware, again. Well engineered and very good value for money. I think I’ll try one of their OLinuXino boards later on too.

After trying a simple rule 110 cellular automaton as a first test for the display. I’ve now ported my Bash implementation of Craig Reynolds Boids to the MSP430 microcontroller.

A video follows after the break:

The bad compact camera video doesn’t really do justice to the effect, which looks a lot better and more fluidic in real life.

The code for this is written in the great Energia IDE, a fork of Arduino for the MSP430 Launchpad.

The code is pretty ugly, as this was a quick two hour hack on the couch last evening. Also like fi.sh, the Bash version, this doesn’t do real vector arithmetic, it just does calculations on the x- and y-components on vectors. Since C is more capable than Bash in this respect, I might update that later, although it isn’t really necessary to achieve the right behaviour.

The full code is included below, put on your peril sensitive sunglasses:

mspboids.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/* 
 * Boids for Olimex MSP430-LED boosterpack and Energia
 * mostly ported from fi.sh 
 * (http://isquared.nl/blog/2010/02/22/fi.sh-a-Boids-clone-in-Bash/)
 *
 * Hessel Schut, hessel@isquared.nl, 2013-04-07
 */

// The 8x8 led matrix
#define LATCH 6
#define CLOCK 7
#define DATA 14
#define DELAYTIME 1 // microseconds to wait after setting pin

// MIC seeds random()
#define AIN 0 // Note: AIN0 == pin2

// number of boids
#define NBOIDS 4

// minimum separation for avoid()
#define SEP 3

// frame buffer 
unsigned char image[8] = {
  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
};

// coordinates
typedef struct point {
  unsigned short x;
  unsigned short y;
};

// vectors
typedef struct vector {
  signed short x;
  signed short y;
};

// boids 
typedef struct boid  {
  point position;
  vector heading;
};

// boid instances
boid boids[NBOIDS] = {};

// clear framebuffer
void clearScreen() {
  for (int i = 0; i < 8; i++) image[i] = 0x00;
}

void setup()
{
  // set pin modes
  pinMode(CLOCK, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  analogReference(INTERNAL1V5);

  // initialize boids
  boids[0].position = (point){0, 3};
  boids[0].heading = (vector){1, 0};
  //
  boids[1].position = (point){7, 1};
  boids[1].heading = (vector){-1, 1};
  // 
  boids[2].position = (point){3, 7};
  boids[2].heading = (vector){1, -1};
  // 
  boids[3].position = (point){5, 4};
  boids[3].heading = (vector){-1, 1};

  // initial display
  plotBoids();
  sendImage(image);
}

// send 16 bits to LED tile (bits 0..7 are column, 8..15 are row) 
void sendData(unsigned short data) {
  for(unsigned short i=0; i<16; i++) {
      digitalWrite(DATA, (data & ((unsigned short)1<<i))?HIGH:LOW);
      delayMicroseconds(DELAYTIME);
      digitalWrite(CLOCK,HIGH);
      delayMicroseconds(DELAYTIME);
      digitalWrite(CLOCK,LOW);
  };
  delayMicroseconds(DELAYTIME);
  digitalWrite(LATCH,HIGH);
  delayMicroseconds(DELAYTIME);
  digitalWrite(LATCH,LOW);
}

void setPixel(unsigned short x, unsigned short y) {
  image[y] |= 1 << x;
  if (y > 0)
      sendImage(image);
      delay(10);
  }; // try to compensate for bright col 0
}

// scan an image on the LED tile
// 8x8 bitmap img is the input
void sendImage(unsigned char *img) {
  unsigned short data;
  for(int i=0;i<8;i++) {
      data = (1<<8)<<i;
      data |= *img++;
      sendData(data);
  };
}

void plotBoids() {
  clearScreen();
  for (int i = 0; i < NBOIDS; i++)
      setPixel(boids[i].position.x, boids[i].position.y);
  sendImage(image);
}

struct point handleEdge(struct point p, struct vector d) {
  return {(p.x + d.x)%8, (p.y + d.y)%8};
}

struct vector flock(unsigned short me) {
  vector flocking_vector = (vector){0, 0};

  for(int peer = 0; peer < NBOIDS; peer++) {
      // skip myself  
      if (peer == me) continue;

      // calculate peer x/y distances
      vector peer_vec = (vector){
          boids[peer].position.x - boids[me].position.x,
          boids[peer].position.y - boids[me].position.y
      };

      // look only in flight direction
      if (boids[me].heading.x > 0 && peer_vec.x < 0) continue;
      if (boids[me].heading.x < 0 && peer_vec.x > 0) continue;
      if (boids[me].heading.y > 0 && peer_vec.x < 0) continue;
      if (boids[me].heading.y < 0 && peer_vec.x > 0) continue;

      // handle screen wrap-around, wrap for peers on opposite screen half
      point peer_pos = boids[peer].position;
      if (2 * peer_pos.x > 8) peer_pos.x = 8 - peer_pos.x;
      if (2 * peer_pos.y > 8) peer_pos.y = 8 - peer_pos.y;

      // sum flocking_vector, attraction fades with distance
      if (peer_pos.x < boids[me].position.x)
          flocking_vector.x -= 8/(boids[me].position.x - peer_pos.x);
      if (peer_pos.x > boids[me].position.x)
          flocking_vector.x += 8/peer_vec.x;
      if (peer_pos.y < boids[me].position.y)
          flocking_vector.y -= 8/(boids[me].position.y - peer_pos.y);
      if (peer_pos.y > boids[me].position.y)
          flocking_vector.y += 8/peer_vec.y;   
  };
  return flocking_vector;
}

struct vector avoid(unsigned short me) {

  vector avoidance_vector = (vector){0, 0};

  for(int peer = 0; peer < NBOIDS; peer++) {
      // skip myself  
      if (peer == me) continue;

      // calculate peer x/y distances
      vector peer_vec = (vector){
          boids[peer].position.x - boids[me].position.x,
          boids[peer].position.y - boids[me].position.y
      };

      // look only in flight direction
      if (boids[me].heading.x > 0 && peer_vec.x < 0) continue;
      if (boids[me].heading.x < 0 && peer_vec.x > 0) continue;
      if (boids[me].heading.y > 0 && peer_vec.x < 0) continue;
      if (boids[me].heading.y < 0 && peer_vec.x > 0) continue;

      // avoid() only acts above threshold
      if ( abs(peer_vec.x) <= SEP) {
          // safeguard against division by zero
          if (peer_vec.x == 0) peer_vec.x = -1;
          // if (peer_vec.x == 0) continue;
          // subtract from avoidance vector, move away from peer
          avoidance_vector.x -= 8 / peer_vec.x;
      };
      // avoid() only acts above threshold
      if ( abs(peer_vec.y) <= SEP) {
          // safeguard against division by zero  
          if (peer_vec.y == 0) peer_vec.y = -1;
          // if (peer_vec.y == 0) continue;
          // subtract from avoidance vector, move away from peer
          avoidance_vector.y -= 8 / peer_vec.y;
      };
  };
  return avoidance_vector;
}

void calculateBoids() {
  vector avoid_vec, flock_vec;
  vector sum = {0, 0};

  for (int b = 0; b < NBOIDS; b++) {

       // calculate behaviors
       avoid_vec = avoid(b);
       flock_vec = flock(b);
      
       // weighted sum of behaviors, plus random default direction
       sum.x = random(3) + 3 * flock_vec.x + 2 * avoid_vec.x;
       sum.y = random(2) + 3 * flock_vec.y + 2 * avoid_vec.y;

      /* sum = { 
         random(3) + 3 * flock(b).x + 2 * avoid(b).x,
         random(2) + 3 * flock(b).y + 2 * avoid(b).y 
     }; */

      // make discrete heading
      boids[b].heading = (vector){
          (sum.x == 0)?0:(sum.x < 0)?-1:1,
          (sum.y == 0)?0:(sum.y < 0)?-1:1      
      };

      // update position
      boids[b].position = handleEdge(boids[b].position, boids[b].heading);
  };
}

void loop() {
  // toggle red LED on each iteration
  digitalWrite(RED_LED, !digitalRead(RED_LED));
  if (random(10) < 2) randomSeed(analogRead(AIN));

  calculateBoids();
  plotBoids();

  delay(15);
}

Comments