Luteijn/Vortex: Difference between revisions

From RevSpace
Jump to navigation Jump to search
No edit summary
No edit summary
 
(39 intermediate revisions by the same user not shown)
Line 17: Line 17:
http://wiki.dfrobot.com.cn/images/1/10/%E4%B8%BB%E6%9D%BF.png
http://wiki.dfrobot.com.cn/images/1/10/%E4%B8%BB%E6%9D%BF.png


==== Digital Pins ===
==== What's connected to the pins ====
{| class="wikitable"
{| class="wikitable"
|+ What's connected where?!
|+ Connection overview
|-
|-
|0
|0
|Serial TX/RX
|Serial RX (input)
|-
|-
|1
|1
|Serial RX/TX
|Serial TX (output)
|-
|-
|2
|2
|Encoder Wheel (External Interrupt 0)
|Encoder Wheel (External Interrupt 0) (input)
|-
|-
|3
|3
|Encoder Wheel (External Interrupt 1)
|Encoder Wheel (External Interrupt 1) (input)
|-
|-
|4
|4
|
|Unknown, seems to be pulled high when used as input. Found it too hard to trace on the board.
|-
|-
|5
|5
|
|Motor 0 Speed (PWM) (output)
|-
|-
|6
|6
|
|Motor 1 Speed (PWM) (output)
|-
|-
|7
|7
|IR decoder/detector
|IR decoder/detector (input)
|-
|-
|8
|8
|
|IR led Left (output)
|-
|-
|9
|9
|
|Motor 0 direction (H=forwards,L=reverse) (output)
|-
|-
|10
|10
|
|Motor 1 direction (output)
|-
|-
|11
|11
|
|MP3-player TX (output)
|-
|-
|12
|12
|
|IR led Right (output)
|-
|-
|13
|13
|
|GRB LED chain around body (output)
|-
|A0
|grayscale 4 'D' (input)
|-
|A1
|grayscale 3 'C' (input)
|-
|A2
|grayscale 2 'B' (input)
|-
|A3
|grayscale 1 'A' (input)
|-
|A4
|SDA (i2c)
|-
|A5
|SCL (i2c)
|-
|A6
|grayscale 5 'E' (input)
|-
|A7
|grayscale 6 'F' (input)
|-
|-
|}
i2c bus + Vcc & Gnd should be available at 4 pin rear expansion port too - need to find a suitable connector for it..
http://www.hobbytronics.co.uk/arduino-atmega328-pinout Useful diagrams to map pins to package
==== Dumping original firmware ====
Although you can/should be able to reflash Vortex with the App, might as well make a backup of the orignal:
avrdude -c arduino -p m328p -P /dev/ttyACM0 -U flash:r:"Vortex.hex":i
This can be uploaded again with:
avrdude -c arduino -p m328p -P /dev/ttyACM0 -U flash:w:"Vortex.hex":i
(The app might also be initializing other things, but this seems to work)
Your own sketches can be uploaded with
arduino --upload MySketch.ino
But using avrdude to upload the .hex file is a bit quicker.


==== Motor Control ====
==== Motor Control ====
Line 194: Line 234:
</pre>
</pre>


Wonder what's connected to A4 and A5, if anything.
Wonder what's connected to A4 and A5, if anything? These are used for the i2c bus! See below under the [[Luteijn/Vortex#Eyes]] section


====LEDs====
====LEDs====
Line 204: Line 244:


====IR obstacle detector====
====IR obstacle detector====
Seems to be somewhat flaky. Needs a bit more work to figure out. Also, the IR sensor might be useful to read IR remote controllers or beacons. This receiver seems to work at 38kHz, but probabbly also reacts to 36-40kHz. Remote controllers often work at 36kHz. Note that 2x 8µs delay doesn't give you 38kHz, but the digitalWrite itself also induces quite some delay (3-6µs), so seems this is bringing the total period up to around the 26.something µs we'd expect.
Seems to be somewhat flaky. Needs a bit more work to figure out. Also, the IR sensor is useful to read IR remote controllers or beacons. This receiver seems to work at 38kHz, but probabbly also reacts to 36-40kHz. Remote controllers often work at 36kHz. Note that 2x 8µs delay doesn't give you 38kHz, but the digitalWrite itself also induces quite some delay (3-6µs), so seems this is bringing the total period up to around the 26.something µs we'd expect.




Line 325: Line 365:
</pre>
</pre>
This was already useful to read codes from the remote of the AV-receiver we have at home and then play these back via a universal remote app on the smartphone that was missing some buttons. For some reason I couldn't find the 'learn' function in the app, but could enter the codes as read by this sketch easily.
This was already useful to read codes from the remote of the AV-receiver we have at home and then play these back via a universal remote app on the smartphone that was missing some buttons. For some reason I couldn't find the 'learn' function in the app, but could enter the codes as read by this sketch easily.
The receiver is mounted on the front, bottom. So not right between the eyes, although a spot seems to be prepared for it there too.
The tables at [[Luteijn/IRRemotes]] might be useful when creating a sketch that is to be controlled over IR.
Here's an example of a simple driving sketch controlled via my RTeL-Cheapo remote.
<pre>
#include "IRremote.h"
#define IR_IN  (7)//IR receiver pin
#define E1 (5)
#define E2 (6)
#define M1 (9)
#define M2 (10)
IRrecv irrecv(IR_IN);
decode_results results;
// MSB is direction, 0xff full forward, 0x00 full backwards
uint8_t Left=128; // 0x80 : forward, speed 0
uint8_t Right=128; // 0x80 : forward, speed 0
void setup(void){
  delay(2000); // grace period
  Serial.begin(9600);
  pinMode(M1, OUTPUT);
  pinMode(M2, OUTPUT);
  Engine(Left,Right);
  irrecv.enableIRIn(); // Start the receiver
}
void Engine(uint8_t Left, uint8_t Right) {
  uint8_t D1=Left&0x80;
  uint8_t D2=Right&0x80;
  uint8_t S1=D1?Left&0x7f:0x7f-(Left&0x7f);
  uint8_t S2=D2?Right&0x7f:0x7f-(Right&0x7f);
  Serial.print(Left,HEX);
  Serial.print("<-L R->");
  Serial.println(Right,HEX);
  digitalWrite(M1,D1);
  analogWrite(E1,S1<<1);
  digitalWrite(M2,D2);
  analogWrite(E2,S2<<1);
}
uint32_t last;
void loop(void){
  uint32_t code;
  if (code=irrecv.decode(&results)) {
    code=results.value;
    Serial.print("Got IR:");
    Serial.println(code,HEX);
    if (code==0xFFFFFFFF) {code=last;}; // repeated press
    if (code==0xFFB24D) { // power = full stop
      Left=128;
      Right=128;
    } else if (code==0xFF02FD) { // full screen  both go towards full stop
      if (Left<128) Left++; else Left--;
      if (Right<128) Right++; else Right--;
    } else if (code==0xFFA05F) { // ch+ increase both towards full ahead
      if (Left<255) Left++;
      if (Right<255) Right++;
    } else if (code==0xFF40BF) { // ch- decrease both towards full reverse
      if (Left>0) Left--;
      if (Right>0) Right--;
    } else if (code==0xFF50AF) { // vol- left towards full stop
      if (Left<128) Left++; else Left--;
    } else if (code==0xFF32CD) { // record increase left towards full ahead
      if (Left<255) Left++;
    } else if (code==0xFF48B7) { // 0 decrease left towards full reverse
      if (Left>0) Left--;
    } else if (code==0xFF7887) { // vol+ right towards full stop
      if (Right<128) Right++; else Right--;
    } else if (code==0xFF30CF) { // time shift  increase right towards full ahead
      if (Right<255) Right++;
    } else if (code==0xFF38C7) { // recall decrease right towards full reverse
      if (Right>0) Right--;
    } else {
      // ignore unknown codes
    }
    last=code;
    irrecv.resume(); // Receive the next value
    Engine(Left,Right);
  }
}
</pre>
It would be better to use a stronger remote for this as it's hard to successfully control the robot when it's facing away from you. Also, the speed ramp up/down is a bit slow, and should probably take bigger steps than one at a time.
Left as an exercise to the reader: add controls to spin in place left/right, using interrupts of wheel encoders to rotate 45/90 etc. degrees, boundary detection with analogue sensors, object detection with IR (careful not to make control harder). Switch to a bluetooth remote controller once figured out the possibilities of bluetooth better?


====MP3-player====
====MP3-player====
There is an MP3.player integrated in the robot. It can be controlled by sending more or less magic commands over software Serial via pin 11. Pin 2 might be connected to the player too if the example I found can be believed, but my tests so far never had anything received there (like an end of song reached, or even just an 'ack'), or when trying to get version information out. Anyway, D2 is connected to one of the wheel encoders, so not likely to be the RX. Maybe it's 12 or 10?
There is an MP3.player integrated in the robot. It can be controlled by sending more or less magic commands over software Serial via pin 11. Pin 2 might be connected to the player too if the example I found can be believed, but my tests so far never had anything received there, or on pin 4. Would be nice to get an 'end of song reached' or to get things like version information out. Anyway, Pin 2 is connected to one of the wheel encoders, so not likely to be the RX. It might also be co-connected to one of the relatively harmless outputs, like to the ir emitter? Will need to trace the board to find out, but initial quick look didn't immediately show anything.


An example found on the DFRobot site:
The example found on the DFRobot site:
<pre>
<pre>
#define MP3_VOLUME 0x10
#define MP3_VOLUME 0x10
#define TX 11
#define TX 11
#define RX 12 // 2 in example I found
#define RX 4 // ?? was 2 in example I found, but that is unlikely
#include <SoftwareSerial.h>
#include <SoftwareSerial.h>


Line 341: Line 470:
   delay(1000);  
   delay(1000);  
   mp3Init();
   mp3Init();
   mp3setVolume(30);//0~255
   mp3setVolume(30);//0~30
}
}
void loop()
void loop()
Line 374: Line 503:
</pre>
</pre>


The files to play can be put on the Vortex via usb, seems the little switch next to the micro-usb socket switches this between the mp3-players mass-storage and the arduino usb-serial interface. The number passed to the player is just the index into the (FAT?) table of stored songs. So, be careful of the order these are uploaded to the memory in.
The files to play can be put on the Vortex via usb, seems the little switch next to the micro-usb socket switches this between the mp3-players mass-storage and the arduino usb-serial interface. The number passed to the player is just the index into the (FAT?) table of stored songs. So, be careful of the order these are uploaded to the memory in.  


https://www.dfrobot.com/product-1121.html , documented at https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299 is a mini-MP3 player form DFRobot. It is controllable over serial, and might be the same one as used in the Vortex. Would be good to figure out if the 'busy' signal is connected back to the arduino somewhere in that case. Player could also be a variant but the magic numbers more or less match (although the stop command 0x16 is not in the table. Perhaps Dec16 / 0x0A (standby/low power mode) was meant? The MP3-chip Part# is YX6100-24SS. Seems to be part of a family of serial MP3-players.
https://www.dfrobot.com/product-1121.html , documented at https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299 is a mini-MP3 player from DFRobot. It is controllable over serial, and might be the same one as used in the Vortex. Would be good to figure out if the 'busy' signal and/or the serial TX are connected back to the arduino somewhere in that case. Player could also be a variant but the magic numbers more or less match (although the stop command 0x16 is not in the table.  
 
The MP3-chip inside Vortex has Part# YX6100-24SS. Seems to be part of a family of serial MP3-players made by "广州悦欣电子科技有限公司" --> "Guangzhou Yue Xin Electronic Technology Co., Ltd." - website http://www.yx080.com/mp3xinpian/53-15.html has a download link on this yx6100 page but it seems to just refer us to the 5200 application Manual V1.8, which appears to be mostly compatible. The command table for the chip does include a 0x16 stop command.
 
Also the datasheet mentions the chip can play both MP3 and WAV. YX5300-24SS is a similar part, datasheet at https://xp-dev.com/trac/arduino_antonio/export/280/arduino_antonio/trunk/EnterpriseBase/Serial%20MP3%20Player/About%20the%20Chip%20-%20YX5300/YX5300-24SS%20Datasheet%20V1.0.pdf The command table for that chip does include a 0x16 stop command.


Documentation for the DFRobot miniplayer mentions the instruction format to be:
Documentation for the DFRobot miniplayer mentions the instruction format to be:
Line 412: Line 545:
| checksum
| checksum
| Checksum
| Checksum
| accumulation and verification, doesn't include start byte. Seems to be 2 bytes from the example given in the docs, but Vortex seems to just not need it put in. example given is 7e ff 06 09 00 00 04 ff dd ef  
| accumulation and verification, doesn't include start byte. Seems to be 2 bytes from the example given in the docs, but Vortex seems to just not need it put in. example given is 7e ff 06 09 00 00 04 ff dd ef ; The 5200 Datasheet uses the exact same example command, and explains that you should add all the bytes, and then subtract those from 0 to get the checksum. The 5300 Datasheet also mentions "另外用户也可以直接忽视校验,参考我们的5.3.4 章节说明。" so looks like the checksum is optional if you don't care too much about corruption of the occasional command.
|-
|-
| $O
| $O
Line 420: Line 553:


{| class="wikitable"
{| class="wikitable"
|+Command table
|+Command table WIP to copy this over
|-
!CMD
!Function Description
!Parameters (16 bit)
!compared with YX5300 info
|-
|0x01
|Next
|
|-
|0x02
|Previous
|
|-
|0x03
|Specific track
|0-2999 but example is only using low byte. also track 0 doesn't seem to do anything?
|Indicates 1-255 are the valid tracks only.
|-
|0x04
|Volume up
|
|-
|0x05
|Volume down
|
|-
|0x06
|Set Volume
|0-30 (10 is already quite loud!)
|-
|0x07
|Specify EQ(0/1/2/3/4/5/)
|Normal/Pop/Rock/Jazz/Classic/Base.
|Reserved in the 5300's command list.
|-
|0x08
|Specify playback mode (0/1/2/3)
|Repeat/folder repeat/single repeat/random
|See 3.4.3; this explains the argument is the song to play in a loop - may have been changed for 6100
|-
|0x09
|Select source (0/1/2/3/4)
|U/TF/AUX/SLEEP/FLASH
|See 3.4.4; specifies 1 as U, 2 as TF, 4 as PC, 5 FLASH and 6 SLEEP
|-
|0x0a
|Enter into standby - low power loss
|
|Sleep - Low power consumption 10MA (obviously mA or µA, not MA, is meant)
|-
|0x0b
|Normal working
|
|Wake up from sleep
|-
|0x0c
|reset module
|
|chip reset
|-
|0x0d
|Playback
|
|Play - seems to be contrast to suspend/pause
|-
|0x0e
|Pause
|
|Suspended
|-
|0x0f
|Specify folder to playback
|1~10(need to set by user)
|see 3.4.5 - DH: represents the name of the folder, the default support for 99 files, 01 - 99 named DL: represents the track, the default maximum of 255 songs, that is, 0x01 ~ 0xFF
|-
|0x10
|Volume adjust set
|DH=1:open volume adjust DL: set volume gain 0~31
|not present in command table
|-
|0x11
|Repeat play
|1:start repeat play 0: stop play
|not present in command table
|-
|0x16
|not in the table
|example mentions it as a stop command
|Stop
|-
|0x17
|not in the table
|
|FLASH only, see 3.4.7 (but is in section 3.4.6), select folder to loop.
|-
|0x18
|not in the table
|
|Reserved
|-
|0x19
|not in the table
|
|See 3.4.8 (but is in section 3.4.7), select loop current track mode
|-
|0x21
|not in the table
|
|See 3.4.9 (3.4.8), set DAC to High-Z mode (1) or on (0) so you can use the amp for something else.
|-
|0x22
|not in the table
|
|See 3.4.10 (section 3.4.9 doesn't exist), Set volume and song to play in one command (H:volume L:track)
|-
|0x3C
|STAY
|
|Reserved
|-
|0x3D
|STAY
|
|Reserved
|-
|0x3E
|STAY
|
|Reserved
|-
|0x3F
|Send initialization parameters
|0-0x0F (each bit represent one device of the low-four bits)
|Seems to be reporting which storage devices are currently online (see section 3.5.1)
|-
|0x40
|Returns an error, request retransmission
|
|Probably this is a response to a command meaning Error Encountered.
|-
|0x41
|Answer
|
|Probably this is a response to a query and just means Accepted.
|-
|0x42
|Query Status
|
|See  3.4.10 (actually 3.5.2) - explains that this will return which storage is used and if it's playing, stopped or paused. The storage could also be 'SLEEP' to indicate sleeping?
|-
|
|
|
|-
|
|
|
|-
|
|
|
|-
|
|
|
|-
|
|
|
|-
|
|
|
|-
|
|
|
|-
|-
|
|
|
|
|-
|-
|
|
|
|
|-
|
|
|
|-
|
|
|
|-
|
|
|
|}
|}


Line 437: Line 765:
}
}
</pre>
</pre>
 
but this doesn't seem to be needed.
 
 


====Eyes====
====Eyes====
The 'Eyes' are connected over i2c. The daughterboard they are on features an STM8S103K3 (see http://www.st.com/en/microcontrollers/stm8s103-105.html ) and 2x HC595 (shift registers).
Besides 35 predefined eye patterns, it is also possible to upload your own eye patterns.
Besides 35 predefined eye patterns, it is also possible to upload your own eye patterns.


Line 467: Line 794:
void defined_eyes(uint8_t color,uint8_t serial) {
void defined_eyes(uint8_t color,uint8_t serial) {


   Wire.beginTransmission(I2C_LED_ADDRESS << 1 | I2C_WRITE); // transmit to device #4
   Wire.beginTransmission(I2C_LED_ADDRESS << 1 | I2C_WRITE); // transmit to device #4  
// (I suppose they mean 0x40, although eyes seem to also respond on 0xC0 which is what is defined above, and 0x20)
   Wire.write(color&0x07); // color bits: 1 blue, 2 green, 4 red  
   Wire.write(color&0x07); // color bits: 1 blue, 2 green, 4 red  
   Wire.write(serial);  //preset eyes 0~34
   Wire.write(serial);  //preset eyes 0~34
Line 512: Line 840:
}                         
}                         
</pre>
</pre>
=====KITT-scanner / Cylon=====
This animates the eyes and adds some simple sound effects - currently the eye warble is not synchronized to the eye movement. The IR remote is used to trigger the well-known 'By your command'. For a KITT scanner may want to change to the proper woosh for a KITT, and replace the command sample with something like 'yes, Michael' :)
<pre>
#include <Wire.h>
#include <SoftwareSerial.h>
#include <IRremote.h>
#define MTX (11)
#define MRX (4)
// Song number for "By your command"-sample
#define BYC (30)
// Song number for Warble
#define Warble (31)
#define IR_IN (7)
#define I2C_LED_ADDRESS 0x80
bool left=1;
decode_results results;
SoftwareSerial mp3(MRX,MTX);
IRrecv irrecv(IR_IN);
short command=0;
uint8_t eyes[10]={0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
void setup(){
  Wire.begin(); // join i2c bus (address optional for master)
  mp3Init();
  irrecv.enableIRIn(); // Start the receiver
  delay(2000);
  delay(100);
  mp3setVolume(10);//0~255, but 30 is already quite loud!
  delay(50);
  mp3player(Warble);
  delay(50);
  mp3repeat(2); // repeat once
  delay(50);
  mp3player(BYC);
  delay(3000);
  mp3setVolume(8);//0~255, but 30 is already quite loud!
  delay(50);
  mp3player(Warble);
  delay(50);
  mp3repeat(0); // repeat forever
  delay(50);
}
void lscroll_eyes(uint8_t * eyes ){
  uint8_t i;
  eyes[2]=(uint8_t)(eyes[2]<<1);
  eyes[7]=(uint8_t)(eyes[7]<<1);
}
void rscroll_eyes(uint8_t * eyes ){
  uint8_t i;
  eyes[2]=(uint8_t)(eyes[2]>>1);
  eyes[7]=(uint8_t)(eyes[7]>>1);
}
void custom_eyes(uint8_t color, uint8_t eyeline[10]){
  uint8_t index;
  Wire.beginTransmission(I2C_LED_ADDRESS); // 0x4 preshifted to 0x8 and lsb set to 0 for write
  Wire.write(0x55);  // register 55=?custom eyes
  Wire.write(0xAA); // ?? AA=color followed by 10 bytes, each defining one line
  Wire.write(color&0x7);        //color 1-B,2-G,4-R
  for (index=0;index<10;index++) {
    Wire.write(eyeline[index]);
  }
  Wire.endTransmission();    // stop transmitting
}
void loop(){
  // 1 2 4 8 10 '20' 10 8 4 2 1
  if (left) {lscroll_eyes(eyes);}
  else if (!left) {rscroll_eyes(eyes);}
  custom_eyes(4,eyes);
  if (eyes[7]==0x1) {left=1;}
  else if (eyes[2]==0x1) {left=1;}
  else if (left && eyes[2]>=0x10) {eyes[2]=0;eyes[7]=0x20;left=0;}
  else if (left && eyes[7]>=0x10) {eyes[7]=0;eyes[2]=0x20;left=0;}
  if (irrecv.decode(&results)){
    if (command) {
      command=0;
    } else{
      command=34; // adjust to match sample length
      mp3player(BYC);
      delay(50);
    }
    irrecv.resume();
  } else {
    if (command) {
      command--;
      if (!command){
        mp3player(Warble);
        delay(50);
        mp3repeat(0); // repeat forever
      } else {
        delay(80);
      }
    } else {
      delay(80);
    }
  }
}
void mp3Init()
{
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x0C, 0x00, 0x00, 0x00, 0xef};
  mp3.begin(9600);
  delay(100);
  mp3.write(buffer, 8);
  delay(100);
}
void mp3setVolume(byte vol)
{
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x06, 0x00, 0x00, vol, 0xef};
  mp3.write(buffer, 8);
}
void mp3player(byte data)
{
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x03, 0x00, 0x00, data, 0xef};
  mp3.write(buffer, 8);
}
void mp3repeat(byte data)
{
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x08, 0x00, 0x00, data, 0xef};
  mp3.write(buffer, 8);
}
</pre>
===== Conway's Game of Life =====
2 5x5 fields linked as a torus of 10x5. Change mapping of cells in the edge to make e.g. a front and a back side, or change to 8x8 and add 4 more faces and a bit more mapping to create an LED cube of 6 faces. Simple animation on the 2 eye matrices. This version doesn't actually do something with the remote control, but it was easy to add things like reset board, change color, introduce disturbance etc.
<pre>
#include <Wire.h>
#include <IRremote.h>
#define IR_IN (7)
#define I2C_LED_ADDRESS 0x80
// 5x5 matrix eye
#define CX (5)
#define CY (5)
// edge of 1 cell on each end
#define CXM (7)
#define CYM (7)
#define F (2) // 2 faces have I.
decode_results results;
IRrecv irrecv(IR_IN);
uint8_t eyes[10];
uint8_t board_a[F][CYM][CXM];
uint8_t board_b[F][CYM][CXM];
inline void clear(uint8_t board[][CYM][CXM]) {
  uint8_t i,j,k;
  for (k=0;k<F;k++) {
    for (j=0;j<CYM;j++) {
      for (i=0;i<CXM;i++) {
        board[k][j][i]=0;
      }
    }
  }
}
inline uint8_t neighbors(uint8_t s[][CYM][CXM], uint8_t k,uint8_t j,uint8_t i) {
  return s[k][j-1][i-1]+s[k][j-1][i]+s[k][j-1][i+1]+s[k][j][i-1]+s[k][j][i+1]+s[k][j+1][i-1]+s[k][j+1][i]+s[k][j+1][i+1];
}
inline void play(uint8_t s[][CYM][CXM], uint8_t d[][CYM][CXM]) {
  uint8_t i,j,k;
// TODO: load edges from right faces hardcoded for 5x5 now
  for (i=1;i<=5;i++) {
    s[0][0][i]=s[0][5][i];
    s[0][6][i]=s[0][1][i];
    s[0][i][0]=s[1][i][5];
    s[0][i][6]=s[1][i][1];
    s[1][0][i]=s[1][5][i];
    s[1][6][i]=s[1][1][i];
    s[1][i][0]=s[0][i][5];
    s[1][i][6]=s[0][i][1];
  }
  // corners
    s[0][0][0]=s[1][5][5];
    s[0][6][0]=s[1][1][5];
    s[0][0][6]=s[1][5][1];
    s[0][6][6]=s[1][1][1];
    s[1][0][0]=s[0][5][5];
    s[1][6][0]=s[0][1][5];
    s[1][0][6]=s[0][5][1];
    s[1][6][6]=s[0][1][1];
// update interior cells
  for (k=0;k<F;k++) {
    for (j=1;j<=CY;j++) {
      for (i=1;i<=CX;i++) {
        switch (neighbors(s,k,j,i)) {
          case 2: d[k][j][i]=s[k][j][i]; // remain
                  break;
          case 3: d[k][j][i]=1; // birth
                  break;
          default: d[k][j][i]=0; // death
        }
      }
    }
  }
}
inline uint8_t convert_l(uint8_t l[CXM]) {
return (l[5]|l[4]<<1|l[3]<<2|l[2]<<3|l[1]<<4);
}
inline uint8_t convert_r(uint8_t l[CXM]) {
return (l[1]|l[2]<<1|l[3]<<2|l[4]<<3|l[5]<<4);
}
inline uint8_t * board_to_eyes(uint8_t board[F][CYM][CXM]) {
  static uint8_t eyes[10]={0,0,0,0,0,0,0,0,0,0};
  eyes[0]=convert_l(board[1][5]);
  eyes[1]=convert_l(board[1][4]);
  eyes[2]=convert_l(board[1][3]);
  eyes[3]=convert_l(board[1][2]);
  eyes[4]=convert_l(board[1][1]);
  eyes[5]=convert_r(board[0][1]);
  eyes[6]=convert_r(board[0][2]);
  eyes[7]=convert_r(board[0][3]);
  eyes[8]=convert_r(board[0][4]);
  eyes[9]=convert_r(board[0][5]);
  return eyes;
}
void dump(uint8_t b[][CYM][CXM]) {
  uint8_t i,j,k;
  Serial.println("==");
  for (k=0;k<F;k++) {
    for (j=1;j<=CY;j++) {
      for (i=1;i<=CX;i++) {
        Serial.print(b[k][j][i]?'x':'o');
      }
      Serial.println("");
    }
      Serial.println("==");
  }
}
void setup(){
  delay(2000);
  Serial.begin(9600);
  Serial.println("life");
  Wire.begin(); // join i2c bus (address optional for master)
  irrecv.enableIRIn(); // Start the receiver
  clear(board_a); // start with empty board a
  clear(board_b); // start with empty board b
  //some initial state:
  board_a[0][1][2]=1;
  board_a[0][2][3]=1;
  board_a[0][3][1]=1;
  board_a[0][3][2]=1;
  board_a[0][3][3]=1;
  board_a[1][1][2]=1;
  board_a[1][2][3]=1;
  board_a[1][3][1]=1;
  board_a[1][3][2]=1;
  board_a[1][3][3]=1;
  Serial.println("inited");
}
inline void custom_eyes(uint8_t color, uint8_t eyeline[10]){
  uint8_t index;
  // right eye          left eye
  //1  2  3  4  5      25 24 23 22 21
  //6  7  8  9  10    20 19 18 17 16
  //11 12 13 14 15    15 14 13 12 11
  //16 17 18 19 20    10 9  8  7  6
  //21 22 23 24 25    5  4  3  2  1
  Wire.beginTransmission(I2C_LED_ADDRESS); // 0x4 preshifted to 0x8 and lsb set to 0 for write
  Wire.write(0x55);  // register 55=?custom eyes
  Wire.write(0xAA); // ?? AA=color followed by 10 bytes, each defining one line
  Wire.write(color&0x7);        //color 1-B,2-G,4-R
  for (index=0;index<10;index++) {
    Wire.write(eyeline[index]);
  }
  Wire.endTransmission();    // stop transmitting
}
void loop(){
//  dump(board_a);
  custom_eyes(4,board_to_eyes(board_a));
  play(board_a,board_b);
  delay(50);
//  dump(board_b);
  custom_eyes(4,board_to_eyes(board_b));
  play(board_b,board_a);
  delay(50);
//TODO: add something to react to remote here, e.g. set some cells etc.
  if (irrecv.decode(&results)){
    irrecv.resume();
  } else {
  }
}
</pre>
===== Countdown Timer =====
This sketch turns Vortex into an eggtimer to be used to keep track of how much time you have left when giving a talk, or before your food is ready. It displays the time left on the eyes, and can be controlled with a IR remote to (re)set the timer. LED colors are adjusted based on  the time left, with vortex blinking red when you're out of time.
Below the timer is set up to count minutes, but you can of course change the number of milliseconds per 'tick' to count seconds or whatever. Adding things like rotating lights to indicate progress of time or playing sound effects if desired are left to the reader, this is more or less a demo of using the eyes as a digit display.
Because the two eyes are rotated 180 degrees compared to each other, there is code to swap bit-patterns (for the full 5 pixels per row, even if the digits here only use 3 per row). The font is rotated at run-time in setup(), so it's easier to tweak the font.
Make sure to restart Vortex in time for the 55 day limit on the millis() roll-over not to affect things, or add code to detect and handle the rollover.
The code to work with a remote is set up for a specific remote controller (RC 240 in TV mode), so you probably have to adjust this bit to work with the controller you intend to use.
<pre>
#include <Wire.h>                                                                                 
#include <IRremote.h>                                                                             
#include <FastLED.h>                                                                               
//IR receiver pin                                                                                 
#define IR_IN  (7)                                                                                 
#define I2C_LED_ADDRESS 0b100000                                                                   
#define I2C_WRITE  0x00                                                                           
// How many leds are in the strip?                                                                 
#define NUM_LEDS 12                                                                               
// Data pin that led data will be written out over                                                 
#define DATA_PIN 13                                                                               
CRGB leds[NUM_LEDS];                                                                         
// how many milliseconds per count (1000=count seconds, 60000 count minutes)                       
unsigned long MperC=60000;                                                                         
                                                                                                   
unsigned long start;                                                                               
short countdown;                                                                                   
short displayed;                                                                                   
uint8_t state=0;                                                                                   
unsigned long m_elapsed;                                                                           
short c_elapsed;                                                                                   
unsigned long long current=0xffffffff;                                                             
unsigned long long last=0xffffffff;                                                               
                                                                                                   
IRrecv irrecv(IR_IN);                                                                             
decode_results results;                                                                           
                       
uint8_t eyes[10]={0,0,0,0,0,0,0,0,0,0};                                                           
                                                                                                   
uint8_t left_digit[10][5]={                                                                       
    {0x04,0x0A,0x0A,0x0A,0x04}, //0                                                               
    {0x0E,0x04,0x04,0x0C,0x04}, //1                                                               
    {0x0E,0x08,0x04,0x02,0x0C}, //2                                                               
    {0x0C,0x02,0x0C,0x02,0x0C}, //3                                                               
    {0x02,0x02,0x0E,0x0A,0x0A}, //4                                                               
    {0x0E,0x02,0x0C,0x08,0x0E}, //5                                                               
    {0x04,0x0A,0x0C,0x08,0x04}, //6                                                               
    {0x04,0x04,0x02,0x02,0x0C}, //7                                                               
    {0x04,0x0A,0x04,0x0A,0x04}, //8                                                               
    {0x04,0x02,0x06,0x0A,0x04}  //9                                                               
};                                                                                                 
uint8_t right_digit[10][5];                                                                       
uint8_t swap(uint8_t l) {                                                                         
    switch (l&0x1f){                                                                               
        case 0x0: return 0;                                                                       
        case 0x01: return 0x10;                                                                   
        case 0x02: return 0x08;                                                                   
        case 0x03: return 0x18;                                                                   
        case 0x04: return 0x04;                                                                   
        case 0x05: return 0x14;                                                                   
        case 0x06: return 0x0C;                                                                   
        case 0x07: return 0x1C;                                                                   
        case 0x08: return 0x02;                                                                   
        case 0x09: return 0x12;                                                                   
        case 0x0a: return 0x0A;                                                                   
        case 0x0b: return 0x1A;                                                                   
        case 0x0c: return 0x06;                                                                   
        case 0x0d: return 0x16;                                                                   
        case 0x0e: return 0x0E;                                                                   
        case 0x0f: return 0x1E;                                                                   
        case 0x10: return 0x01;                                                                   
        case 0x11: return 0x11;                                                                   
        case 0x12: return 0x09;                                                                   
        case 0x13: return 0x19;                                                                   
        case 0x14: return 0x05;                                                                   
        case 0x15: return 0x15;                                                                   
        case 0x16: return 0x0D;                                                                   
        case 0x17: return 0x1d;                                                                   
        case 0x18: return 0x03;                                                                   
        case 0x19: return 0x13;                                                                   
        case 0x1a: return 0x0B;                                                                   
        case 0x1b: return 0x1B;                                                                   
        case 0x1c: return 0x07;                                                                   
        case 0x1d: return 0x17;                                                                   
        case 0x1e: return 0x0F;                                                                   
        case 0x1f: return 0x1F;                                                                   
        default: return 0;                                                                         
    }                                                                                             
}                                                                                                 
void setup(){                                                                                     
    char i,j;                                                                                     
    delay(2000);                                                                                   
    FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);                                       
    for (i=0;i<10;i++){                                                                           
        for (j=0;j<5;j++) {                                                                       
            right_digit[i][j]=swap(left_digit[i][4-j]);                                           
        }                                                                                         
    }                                                                                             
    irrecv.enableIRIn();                                                                           
    Wire.begin(); // join i2c bus (address optional for master)                                   
    FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);                                       
    for (i=0;i<12;i++){                                                                           
        leds[i]=CRGB::Green;                                                                       
    }                                                                                             
    FastLED.show();                                                                               
    start=millis();                                                                               
    displayed=100;                                                                                 
    countdown=99;                                                                                 
    state=0;                                                                                       
}
void custom_eyes(uint8_t color, uint8_t eyeline[10]){                                             
  uint8_t index;                                                                                 
  // right eye        left eye                                                                   
  //1  2  3  4  5      25 24 23 22 21                                                             
  //6  7  8  9  10    20 19 18 17 16                                                             
  //11 12 13 14 15    15 14 13 12 11                                                             
  //16 17 18 19 20    10 9  8  7  6                                                             
  //21 22 23 24 25    5  4  3  2  1                                                             
                                                                                                   
  Wire.beginTransmission(I2C_LED_ADDRESS << 1 | I2C_WRITE); // transmit to device #4             
  Wire.write(0x55);  // color 55=custom eyes follow                                               
  Wire.write(0xAA); // ?? AA=color followed by 10 bytes, each defining one line                   
  Wire.write(color&0x7);        //color 1-B,2-G,4-R                                             
                                                                                                   
  for (index=0;index<10;index++) {                                                               
    Wire.write(eyeline[index]);                                                                   
  }                                                                                               
  Wire.endTransmission();    // stop transmitting                                                 
  // A9 = some sort of multicolor mode                                                           
  // AA = one color byte, then 10 bytes defining the 2 eyes.                                     
  // AB = some sort of multicolor mode                                                           
}                                                                                                 
void update_display(short displayed) {                                                             
    byte h=displayed/10;                                                                           
    byte l=displayed%10;                                                                           
    eyes[0]=left_digit[l][0];                                                                     
    eyes[1]=left_digit[l][1];                                                                     
    eyes[2]=left_digit[l][2];                                                                     
    eyes[3]=left_digit[l][3];                                                                     
    eyes[4]=left_digit[l][4];                                                                     
    eyes[5]=right_digit[h][0];                                                                     
    eyes[6]=right_digit[h][1];                                                                     
    eyes[7]=right_digit[h][2];                                                                     
    eyes[8]=right_digit[h][3];                                                                     
    eyes[9]=right_digit[h][4];                                                                     
    if (displayed<5) {                                                                             
        custom_eyes(4,eyes);                                                                       
    } else {                                                                                       
        custom_eyes(2,eyes);                                                                       
    }                                                                                             
}                                                                                                 
                                                                                                   
void update_leds(short displayed) {                                                               
    char i;                                                                                       
    if (displayed>5) {                                                                             
        for (i=0;i<12;i++){                                                                       
            leds[i]=CRGB(0,displayed*2,0);                                                         
        }                                                                                         
    } else if (displayed>0) {                                                                     
        for (i=0;i<12;i++){                                                                       
            leds[i]=0xff9900;                                                                     
        }                                                                                         
    } else if (state==1) {                                                                         
        for (i=0;i<6;i++) {                                                                       
            leds[i]=CRGB::Red;                                                                     
            leds[i+6]=CRGB::Black;                                                                 
        }                                                                                         
        state=0;                                                                                   
    } else if (state==0) {                                                                         
        for (i=0;i<6;i++) {                                                                       
            leds[i+6]=CRGB::Red;                                                                   
            leds[i]=CRGB::Black;                                                                   
        }                                                                                         
        state=1;                                                                                   
    }                                                                                             
    FastLED.show();                                                                               
}
void loop(){                                                                                       
    unsigned long m_elapsed=millis()-start;                                                       
    // FIXME handle overflow of millis.                                                           
    short c_elapsed=(m_elapsed/MperC);                                                             
    if (displayed>0) {                                                                             
        if (countdown-c_elapsed!=displayed) {                                                     
            displayed=countdown-c_elapsed;                                                         
            displayed=displayed<0?0:displayed;                                                     
            update_display(displayed);                                                             
        }                                                                                         
    }                                                                                             
    update_leds(displayed);                                                                       
    //TODO check IR commands                                                                       
    if (irrecv.decode(&results)) {                                                                 
        current=results.value;                                                                     
        if (current!=last) {                                                                       
            last=current; // store current key to be able to recognize repeats                     
            current=current&0xfffff7ff; // filter out repeat bit. This is for RC5.                 
            if (current==0xa) { // 'clear' on my particular remote                                 
                countdown=0;                                                                       
            } else if (current==0xf) { // 'memo'                                                   
                start=millis(); // reference time is now                                           
                displayed=99;  // restarts timer with current countdown setting                   
            } else if (current==0x0) { // 'digit 0', not all remotes might be this regular         
                digit(0);                                                                         
            } else if (current==0x1) {                                                             
                digit(1);                                                                         
            } else if (current==0x2) {                                                             
                digit(2);                                                                         
            } else if (current==0x3) {                                                             
                digit(3);                                                                         
            } else if (current==0x4) {                                                             
                digit(4);                                                                         
            } else if (current==0x5) {                                                             
                digit(5);                                                                         
            } else if (current==0x6) {                                                             
                digit(6);                                                                         
            } else if (current==0x7) {                                                             
                digit(7);                                                                         
            } else if (current==0x8) {                                                             
                digit(8);                                                                         
            } else if (current==0x9) {                                                             
                digit(9);                                                                         
            } else if (current==0x0) { // TODO add pause, up/down by 1                             
            }                                                                                     
        }                                                                                         
        irrecv.resume(); // Receive the next value                                                 
    }                                                                                             
    delay(500);                                                                                   
}                                                                                                 
void digit(byte d) {                                                                               
    displayed=0; // stops timer updates to display                                                 
    countdown=(countdown%10)*10+d;                                                                 
    update_display(countdown);                                                                     
}
</pre>
==== i2c ====
There might be more stuff connected to the i2c already, I plan to do some tests to find out. Would be nice if e.g. the flash of the mp3 player or some other expansion was already there.
Regardless of what's there already, Vortex seems to be designed to be extended over i2c, and could be a nice platform to do some experiments/demo's with.
Here's a simple scanning sketch, looks like only 0x40 has something connected, and that would be the eye-display.
<pre>
#include <Wire.h>
void setup()
{
  Wire.begin();
  Serial.begin(9600);
  while (!Serial);            // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}
void loop()
{
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 0; address < 128; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.print("  (");
      Serial.print(address,DEC);
      Serial.println("d)  !");
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.print("  (");
      Serial.print(address,DEC);
      Serial.println("d)  !");
    }   
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
  delay(5000);          // wait 5 seconds for next scan
}
</pre>
====Bluetooth====
It seems the Bluetooth functionality is the same as for the "Bluno" and thus the information at https://www.dfrobot.com/wiki/index.php/Bluno_SKU:DFR0267#Wireless_Programming_via_BLE applies.
The AT commands seem to work, up to a point, but looks like it is a somewhat variant version of the firmware as AT+VERSION command doesn't work. Updating program seem not available for Linux.
Booting the Vortex with the 'boot' button (near UL7, Vortex bottom-right-back RGB led.), two leds blink, and linux detects the Vortex over USB as:
[230656.359259] usb 1-1.1: new full-speed USB device number 103 using xhci_hcd
[230656.488502] usb 1-1.1: New USB device found, idVendor=2341, idProduct=0043
[230656.488508] usb 1-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[230656.488511] usb 1-1.1: Product: DFRobot Boot CDC
[230656.488513] usb 1-1.1: Manufacturer: DFRobot BLUno Boot
[230656.490491] cdc_acm 1-1.1:1.0: ttyACM0: USB ACM device
Similarly the little BLE dongle comes up as:
[232626.931574] usb 1-1.3: new full-speed USB device number 108 using xhci_hcd
[232627.254357] usb 1-1.3: New USB device found, idVendor=2341, idProduct=0043
[232627.254362] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[232627.265758] cdc_acm 1-1.3:1.0: ttyACM0: USB ACM device   
If Vortex pairs with the dongle, it effectiely gains an additional, wireless, serial link to a different port on the host. It's connected to the same Serial port on the internal Arduino. Make sure to adjust speed setting to what is used in the currently running sketch, the default 115200 won't work if Serial.begin(9600) is already called..
Using AT commands it should be possible to set up the link connect to other Vortex robots too.
Need to do some experiments to see if it is possible to connect to a generic Bluetooth 4.x dongle too.
====Expansion====
[[file:Vortex expansion holes drawing.svg|right|frame|hole arangement seen from top, vortex facing left or right]]
There are 2 sets of 4 positions for screws/bolts at the top of the vortex, around the battery compartment, obviously to be used to put an expansion unit on top of Vortex, which could be linked to the i2c bus.
=====Dimensions=====
The bigger set of holes are 65 mm apart from left to right and 55 from front to back (center to center). These seem to be meant for 3 mm machine screws
The smaller set of holes are 2.5" apart from left to right and 2.75" from front to back (center to center). These seem to be meant for small screws biting into the plastic.
===== The 4 pin expansion port=====
Q: Does Vortex support sensor module expansion?
A: Vortex has a “Maker mode” where you can connect sensors via its IIC/TWI interface.
It would seem the only logical place for this is the 4 pin connector in the back. Looking at the connector from the back of Vortex:
  ____
|1234|
  -__-
These pins seem to be connected to the rainbow cable coming up from the main PCB as follows:
{| class="wikitable"
|+Vortex expansion port
|-
!Pin!!Function!!Color
|-
|1||SDA?||~15kOhm to Blue
|-
|2||SCL?||Blue
|-
|3||Gnd?||Black
|-
|4||Vcc?||Red
|-
|}
It would make sense for Red to be 5V, Black to be Gnd, and the other two SCL and SDA of the i2c bus. The ~15kOhm between what I suspect to be SCL and SDA would then be two ~7.5kOhm to some common 5 V point that is buffered from the 'Red' cable.
I did some further testing with a known good i2c device connected to the expansion port, but this just hung the i2c scanner or crashed Vortex depending on how I connected the port to the little display. Needs more work...
Both pin 3 and 4 seem to be at +5V to the usb ground

Latest revision as of 07:25, 8 March 2018

Project "Vortex":

Project Luteijn/Vortex
Vortex 1.jpg
Status In progress
Contact Luteijn
Last Update 2018-03-08

I've got four DFRobot 'Vortex' units. Basic programming via the official apps is possible, but rather limited. The WhenDo app is only available on iPad. Luckily, the internal Arduino clone can be directly programmed too. Unfortunately, not all the specs seem to be available, but there are some examples to work with, although they have a quite a bit of 'magic numbers' in them. These examples might disappear, so duplicating them here, with my own notes as available. The examples-coding is mostly done by "Andy Zhou <Andy.zhou@dfrobot.com>", although I did change some of the things to figure out what they do.

http://wiki.dfrobot.com.cn/index.php?title=(SKU:ROB0116)_Vortex%E5%8F%AF%E7%BC%96%E7%A8%8B%E6%9C%BA%E5%99%A8%E4%BA%BA#.E6.A0.B7.E4.BE.8B.E4.BB.A3.E7.A0.81

https://www.dfrobot.com/wiki/index.php/Vortex_Arduino_Coding_Tutorial_V1.0#Introduction

(Chinese page seems to have a bit better documentation, although English page might be easier to read and it is interesting to compare the differences)

http://wiki.dfrobot.com.cn/images/1/10/%E4%B8%BB%E6%9D%BF.png

What's connected to the pins

Connection overview
0 Serial RX (input)
1 Serial TX (output)
2 Encoder Wheel (External Interrupt 0) (input)
3 Encoder Wheel (External Interrupt 1) (input)
4 Unknown, seems to be pulled high when used as input. Found it too hard to trace on the board.
5 Motor 0 Speed (PWM) (output)
6 Motor 1 Speed (PWM) (output)
7 IR decoder/detector (input)
8 IR led Left (output)
9 Motor 0 direction (H=forwards,L=reverse) (output)
10 Motor 1 direction (output)
11 MP3-player TX (output)
12 IR led Right (output)
13 GRB LED chain around body (output)
A0 grayscale 4 'D' (input)
A1 grayscale 3 'C' (input)
A2 grayscale 2 'B' (input)
A3 grayscale 1 'A' (input)
A4 SDA (i2c)
A5 SCL (i2c)
A6 grayscale 5 'E' (input)
A7 grayscale 6 'F' (input)

i2c bus + Vcc & Gnd should be available at 4 pin rear expansion port too - need to find a suitable connector for it..

http://www.hobbytronics.co.uk/arduino-atmega328-pinout Useful diagrams to map pins to package

Dumping original firmware

Although you can/should be able to reflash Vortex with the App, might as well make a backup of the orignal:

avrdude -c arduino -p m328p -P /dev/ttyACM0 -U flash:r:"Vortex.hex":i

This can be uploaded again with:

avrdude -c arduino -p m328p -P /dev/ttyACM0 -U flash:w:"Vortex.hex":i

(The app might also be initializing other things, but this seems to work)

Your own sketches can be uploaded with

arduino --upload MySketch.ino

But using avrdude to upload the .hex file is a bit quicker.

Motor Control

Simple 'enable' and 'direction' pins for left and right wheels. Enable can be PWM'd or just full on. This example revs up the engines with PWM.

int E1 = 5;                                                                                         
int M1 = 9;                                                                                         
int E2 = 6;                                                                                         
int M2 = 10;                                                                                        
                                                                                                    
void setup()                                                                                        
{                                                                                                   
  pinMode(M1, OUTPUT); // directional controls, High is forward, Low is backward                                                                             
  pinMode(M2, OUTPUT);                                                                              
}                                                                                                   
                                                                                                    
void loop()                                                                                         
{                                                                                                   
  int value;                                                                                        
  for(value = 0 ; value <= 255; value+=5)    //foreward                                             
  {                                                                                                 
    digitalWrite(M1,HIGH);                                                                          
    digitalWrite(M2, HIGH);                                                                         
    analogWrite(E1, value);   //PWM Speed control                                                   
    analogWrite(E2, value);   //PWM Speed Control                                                   
    delay(30);                                                                                      
  }                                                                                                 
                                                                                                    
  for(value = 0 ; value <= 255; value+=5)   //backward                                              
  {                                                                                                 
    digitalWrite(M1, LOW);                                                                          
    digitalWrite(M2, LOW);                                                                          
    analogWrite(E1, value);   //PWM Speed control                                                   
    analogWrite(E2, value);   //PWM Speed Control                                                   
    delay(30);                                                                                      
  }                                                                                                 
}                                                    

Encoders

The two external interrupts are linked to encoders that track wheel movement, so it is possible to detect the Vortext is being pushed/pulled/turned, although I don't think these things indicate which direction the robot is pushed in...

#define pinInputLeft 0 // this is the interrupt, not the pin number, D2
#define pinInputRight 1 // external int 1, D3
long leftPul,rightPul;

void leftCallBack(){
  leftPul++;
}

void rightCallBack(){
  rightPul++;
}

void initDdevice(){
    pinMode(5,OUTPUT);
    pinMode(6,OUTPUT);
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);
    noInterrupts();
    attachInterrupt(pinInputLeft,leftCallBack,CHANGE);
    attachInterrupt(pinInputRight,rightCallBack,CHANGE);
    interrupts();
}

void motorDebug(){
  digitalWrite(5,HIGH);
  digitalWrite(6,HIGH);
  digitalWrite(9,HIGH);
  digitalWrite(10,HIGH);
}

void printPul(){
  Serial.print(leftPul);
  Serial.print(" ");
  Serial.println(rightPul);
  leftPul = 0;
  rightPul = 0;
}

void setup() {
  initDdevice();
  Serial.begin(9600);
  motorDebug();
}

void loop() {
  printPul();
  delay(500);
}

Analogue Inputs

six 'grayscale' detectors are placed around the bottom of the Vortex, to be used to track a line, maybe read simple codes from the floor.

 
       2 (a2) 3 (a1)  
1 (a3)               4 (a0)




5 (a6)               6 (a7)

This is the example sketch to dump the values read by the 6 little eyes.

void setup(void){
 Serial.begin(9600); 
}

int analogBuf[6] = {'\0'};

void loop(void){
  analogBuf[0] = analogRead(3);
  analogBuf[1] = analogRead(2);
  analogBuf[2] = analogRead(1);
  analogBuf[3] = analogRead(0);
  analogBuf[4] = analogRead(6);
  analogBuf[5] = analogRead(7);
  for(int i=0;i<6;i++){
    Serial.print(i+1);
    Serial.print(": ");
    Serial.print(analogBuf[i]);
    Serial.print("   ");
  }
  Serial.println();
  delay(500);
}

Wonder what's connected to A4 and A5, if anything? These are used for the i2c bus! See below under the Luteijn/Vortex#Eyes section

LEDs

12 RGB (GRB!) leds can be addressed. Example from website causes Red and Green to be swapped. initialize library differently:

  FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);                                           

IR example below uses some of the LED functionality.

IR obstacle detector

Seems to be somewhat flaky. Needs a bit more work to figure out. Also, the IR sensor is useful to read IR remote controllers or beacons. This receiver seems to work at 38kHz, but probabbly also reacts to 36-40kHz. Remote controllers often work at 36kHz. Note that 2x 8µs delay doesn't give you 38kHz, but the digitalWrite itself also induces quite some delay (3-6µs), so seems this is bringing the total period up to around the 26.something µs we'd expect.


#define NUM_LEDS 12                                                                                 
                                                                                                    
// Data pin that led data will be written out over                                                  
#define DATA_PIN 13                                                                                 
CRGB leds[NUM_LEDS];                                                                                
                                                                                                    
#define IR_IN  7//IR receiver pin                                                                   
#define L_IR 8  //left ir transmitter pin                                                           
#define R_IR 12 //right ir transmitter pin                                                          
                                                                                                    
int count;                                                                                          
                                                                                                    
void leftSend38KHZ(void){//left ir transmitter sends 38kHZ pulse                                    
  int i;                                                                                            
  for(i=0;i<24;i++){                                                                                
    digitalWrite(L_IR,LOW);                                                                         
    delayMicroseconds(8);                                                                           
    digitalWrite(L_IR,HIGH);                                                                        
    delayMicroseconds(8);                                                                           
  }                                                                                                 
}                                                                                                   
void rightSend38KHZ(void){//right ir transmitter sends 38kHZ pulse                                  
  int i;                                                                                            
  for(i=0;i<24;i++){                                                                                
    digitalWrite(R_IR,LOW);                                                                         
    delayMicroseconds(8);                                                                           
    digitalWrite(R_IR,HIGH);                                                                        
    delayMicroseconds(8);                                                                           
  }                                                                                                 
}                                                                                                   
                                                                                                    
void pcint0Init(void){//init the interrupt                                                          
    PCICR |= 1 << PCIE2;                                                                            
    PCMSK2 |= 1 << PCINT23; //pin D7 is int23                                                       
}                                                                                                   
                                                                                                    
ISR(PCINT2_vect){//IR decoder interrupt                                                             
  count++;                                                                                          
}                                                                                                   
                                                                                                    
void obstacleAvoidance(void){                                                                       
  char i;                                                                                           
  count=0;                                                                                          
   leds[5] = CRGB::Green;                                                                           
   FastLED.show();                                                                                  
  for(i=0;i<20;i++){  //left transmitter sends 20 pulses                                            
    leftSend38KHZ();                                                                                
    delayMicroseconds(600);                                                                         
  }                                                                                                 
  if(count>10){//if received a lot pulse , it means there's a obstacle                              
    leds[5] = CRGB::White;                                                                          
    FastLED.show();                                                                                 
    Serial.println("Left");                                                                         
    delay(100);                                                                                     
  }                                                                                                 
  count=0;                                                                                          
  leds[3] = CRGB::Green;                                                                            
  FastLED.show();                                                                                   
  for(i=0;i<20;i++){//right transmitter sends 20 pulses                                             
    rightSend38KHZ();                                                                               
    delayMicroseconds(600);                                                                         
  }                                                                                                 
  if(count>10){//if received a lot pulse , it means there's a obstacle                              
    leds[3] = CRGB::White;                                                                          
    FastLED.show();                                                                                 
    Serial.println("Right");                                                                        
    delay(100);                                                                                     
  }                                                                                                 
  delay(600);                                                                                       
}                                                                                                   
                                                                                                    
void setup(void){                                                                                   
  delay(2000);                                                                                      
  FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);                                           
  pinMode(L_IR,OUTPUT);//init the left transmitter pin                                              
  pinMode(R_IR,OUTPUT);//init the right transmitter pin                                             
  pinMode(IR_IN,INPUT);//init the ir receiver pin                                                   
  Serial.begin(9600);                                                                               
  leds[4] = CRGB::Blue;                                                                             
  FastLED.show();                                                                                   
  delay(2000);                                                                                      
  leds[4] = CRGB::Purple;                                                                           
  FastLED.show();                                                                                   
  noInterrupts();                                                                                   
  pcint0Init();                                                                                     
  interrupts();             //enable the interrupt                                                  
}                                                                                                   
                                                                                                    
void loop(void){                                                                                    
    obstacleAvoidance();                                                                            
}

IRreceiver

Since DFrobot also have a 'loose' IR-receiver module in their program, decided to just see if the example sketches for those would transfer, and they do. https://www.dfrobot.com/product-366.html - A remote similar to the one pictured on the DFRobot product page actually came with one of my RTLSDR sticks, and seems to work well enough, although the range isn't great, couple of meters. With a 'proper' remote control, e.g. from a TV, the range is fine, 10m at least when pointing at unit from across the house. Might be less if using reflections to try to control a roving robot.

So, using the IRremote library you can read values from a remote controller:

#include "IRremote.h"
#define IR_IN  7//IR receiver pin
IRrecv irrecv(IR_IN);
decode_results results;

void setup(void){
  Serial.begin(9600);                                                                               
  irrecv.enableIRIn(); // Start the receiver
}                                                                                                   

void loop(void){                                                                                    
  if (irrecv.decode(&results)) {                                                                    
    Serial.println(results.value, HEX);                                                             
    irrecv.resume(); // Receive the next value
  }                                                                                                 
} 

This was already useful to read codes from the remote of the AV-receiver we have at home and then play these back via a universal remote app on the smartphone that was missing some buttons. For some reason I couldn't find the 'learn' function in the app, but could enter the codes as read by this sketch easily.

The receiver is mounted on the front, bottom. So not right between the eyes, although a spot seems to be prepared for it there too.

The tables at Luteijn/IRRemotes might be useful when creating a sketch that is to be controlled over IR.

Here's an example of a simple driving sketch controlled via my RTeL-Cheapo remote.

#include "IRremote.h"

#define IR_IN  (7)//IR receiver pin
#define E1 (5)
#define E2 (6)
#define M1 (9)
#define M2 (10)

IRrecv irrecv(IR_IN);
decode_results results;
// MSB is direction, 0xff full forward, 0x00 full backwards
uint8_t Left=128; // 0x80 : forward, speed 0
uint8_t Right=128; // 0x80 : forward, speed 0

void setup(void){
  delay(2000); // grace period
  Serial.begin(9600);
  pinMode(M1, OUTPUT);
  pinMode(M2, OUTPUT);
  Engine(Left,Right);
  irrecv.enableIRIn(); // Start the receiver
}

void Engine(uint8_t Left, uint8_t Right) {
  uint8_t D1=Left&0x80;
  uint8_t D2=Right&0x80;
  uint8_t S1=D1?Left&0x7f:0x7f-(Left&0x7f);
  uint8_t S2=D2?Right&0x7f:0x7f-(Right&0x7f);
  Serial.print(Left,HEX);
  Serial.print("<-L R->");
  Serial.println(Right,HEX);
  digitalWrite(M1,D1);
  analogWrite(E1,S1<<1);
  digitalWrite(M2,D2);
  analogWrite(E2,S2<<1);
}
uint32_t last;
void loop(void){
  uint32_t code;
  if (code=irrecv.decode(&results)) {
    code=results.value;
    Serial.print("Got IR:");
    Serial.println(code,HEX);

    if (code==0xFFFFFFFF) {code=last;}; // repeated press

    if (code==0xFFB24D) { // power = full stop
      Left=128;
      Right=128;
    } else if (code==0xFF02FD) { // full screen  both go towards full stop
      if (Left<128) Left++; else Left--;
      if (Right<128) Right++; else Right--;
    } else if (code==0xFFA05F) { // ch+ increase both towards full ahead
      if (Left<255) Left++;
      if (Right<255) Right++;
    } else if (code==0xFF40BF) { // ch- decrease both towards full reverse
      if (Left>0) Left--;
      if (Right>0) Right--;
    } else if (code==0xFF50AF) { // vol- left towards full stop
      if (Left<128) Left++; else Left--;
    } else if (code==0xFF32CD) { // record increase left towards full ahead
      if (Left<255) Left++;
    } else if (code==0xFF48B7) { // 0 decrease left towards full reverse
      if (Left>0) Left--;
    } else if (code==0xFF7887) { // vol+ right towards full stop
      if (Right<128) Right++; else Right--;
    } else if (code==0xFF30CF) { // time shift  increase right towards full ahead
      if (Right<255) Right++;
    } else if (code==0xFF38C7) { // recall decrease right towards full reverse
      if (Right>0) Right--;
    } else {
      // ignore unknown codes
    }
    last=code;
    irrecv.resume(); // Receive the next value
    Engine(Left,Right);
  }
}

It would be better to use a stronger remote for this as it's hard to successfully control the robot when it's facing away from you. Also, the speed ramp up/down is a bit slow, and should probably take bigger steps than one at a time.

Left as an exercise to the reader: add controls to spin in place left/right, using interrupts of wheel encoders to rotate 45/90 etc. degrees, boundary detection with analogue sensors, object detection with IR (careful not to make control harder). Switch to a bluetooth remote controller once figured out the possibilities of bluetooth better?

MP3-player

There is an MP3.player integrated in the robot. It can be controlled by sending more or less magic commands over software Serial via pin 11. Pin 2 might be connected to the player too if the example I found can be believed, but my tests so far never had anything received there, or on pin 4. Would be nice to get an 'end of song reached' or to get things like version information out. Anyway, Pin 2 is connected to one of the wheel encoders, so not likely to be the RX. It might also be co-connected to one of the relatively harmless outputs, like to the ir emitter? Will need to trace the board to find out, but initial quick look didn't immediately show anything.

The example found on the DFRobot site:

#define MP3_VOLUME 0x10
#define TX 11
#define RX 4  // ?? was 2 in example I found, but that is unlikely
#include <SoftwareSerial.h>

SoftwareSerial mySerial(RX, TX);// RX, TX
void setup()
{
  delay(1000); 
  mp3Init();
  mp3setVolume(30);//0~30
}
void loop()
{
  mp3player(1);
  delay(2000);
  mp3stop();
  delay(1000);
}
void mp3Init()
{
    mySerial.begin (9600);
}
void mp3setVolume(byte vol)
{
    uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x06, 0x00, 0x00, vol, 0xef};
    mySerial.write(buffer, 8);
    delay(20);
}
void mp3player(byte data)
{
    uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x03, 0x00, 0x00, data, 0xef};
    mySerial.write(buffer, 8);
    delay(20);
}

void mp3stop()
{
    uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x16, 0x00, 0x00, 0x00, 0xef};
    mySerial.write(buffer, 8);
}

The files to play can be put on the Vortex via usb, seems the little switch next to the micro-usb socket switches this between the mp3-players mass-storage and the arduino usb-serial interface. The number passed to the player is just the index into the (FAT?) table of stored songs. So, be careful of the order these are uploaded to the memory in.

https://www.dfrobot.com/product-1121.html , documented at https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299 is a mini-MP3 player from DFRobot. It is controllable over serial, and might be the same one as used in the Vortex. Would be good to figure out if the 'busy' signal and/or the serial TX are connected back to the arduino somewhere in that case. Player could also be a variant but the magic numbers more or less match (although the stop command 0x16 is not in the table.

The MP3-chip inside Vortex has Part# YX6100-24SS. Seems to be part of a family of serial MP3-players made by "广州悦欣电子科技有限公司" --> "Guangzhou Yue Xin Electronic Technology Co., Ltd." - website http://www.yx080.com/mp3xinpian/53-15.html has a download link on this yx6100 page but it seems to just refer us to the 5200 application Manual V1.8, which appears to be mostly compatible. The command table for the chip does include a 0x16 stop command.

Also the datasheet mentions the chip can play both MP3 and WAV. YX5300-24SS is a similar part, datasheet at https://xp-dev.com/trac/arduino_antonio/export/280/arduino_antonio/trunk/EnterpriseBase/Serial%20MP3%20Player/About%20the%20Chip%20-%20YX5300/YX5300-24SS%20Datasheet%20V1.0.pdf The command table for that chip does include a 0x16 stop command.

Documentation for the DFRobot miniplayer mentions the instruction format to be:

Format: $S VER Len CMD Feedback para1 para2 checksum $O
$S Start byte 0x7e Each command begins with 0x7e ('~')
VER Version information Version seems to be 0xff from the example
Len the number of bytes Start, End and Checksum are not counted,
CMD Commands See command table.
Feedback Command feedback 1: feedback, 0: no feedback ; need to test what this does, might mean 'echo'
Para1 Parameter 1 Query high data byte
Para2 Parameter 2 Query low data byte
checksum Checksum accumulation and verification, doesn't include start byte. Seems to be 2 bytes from the example given in the docs, but Vortex seems to just not need it put in. example given is 7e ff 06 09 00 00 04 ff dd ef ; The 5200 Datasheet uses the exact same example command, and explains that you should add all the bytes, and then subtract those from 0 to get the checksum. The 5300 Datasheet also mentions "另外用户也可以直接忽视校验,参考我们的5.3.4 章节说明。" so looks like the checksum is optional if you don't care too much about corruption of the occasional command.
$O End byte End of command is signaled with 0xEF
Command table WIP to copy this over
CMD Function Description Parameters (16 bit) compared with YX5300 info
0x01 Next
0x02 Previous
0x03 Specific track 0-2999 but example is only using low byte. also track 0 doesn't seem to do anything? Indicates 1-255 are the valid tracks only.
0x04 Volume up
0x05 Volume down
0x06 Set Volume 0-30 (10 is already quite loud!)
0x07 Specify EQ(0/1/2/3/4/5/) Normal/Pop/Rock/Jazz/Classic/Base. Reserved in the 5300's command list.
0x08 Specify playback mode (0/1/2/3) Repeat/folder repeat/single repeat/random See 3.4.3; this explains the argument is the song to play in a loop - may have been changed for 6100
0x09 Select source (0/1/2/3/4) U/TF/AUX/SLEEP/FLASH See 3.4.4; specifies 1 as U, 2 as TF, 4 as PC, 5 FLASH and 6 SLEEP
0x0a Enter into standby - low power loss Sleep - Low power consumption 10MA (obviously mA or µA, not MA, is meant)
0x0b Normal working Wake up from sleep
0x0c reset module chip reset
0x0d Playback Play - seems to be contrast to suspend/pause
0x0e Pause Suspended
0x0f Specify folder to playback 1~10(need to set by user) see 3.4.5 - DH: represents the name of the folder, the default support for 99 files, 01 - 99 named DL: represents the track, the default maximum of 255 songs, that is, 0x01 ~ 0xFF
0x10 Volume adjust set DH=1:open volume adjust DL: set volume gain 0~31 not present in command table
0x11 Repeat play 1:start repeat play 0: stop play not present in command table
0x16 not in the table example mentions it as a stop command Stop
0x17 not in the table FLASH only, see 3.4.7 (but is in section 3.4.6), select folder to loop.
0x18 not in the table Reserved
0x19 not in the table See 3.4.8 (but is in section 3.4.7), select loop current track mode
0x21 not in the table See 3.4.9 (3.4.8), set DAC to High-Z mode (1) or on (0) so you can use the amp for something else.
0x22 not in the table See 3.4.10 (section 3.4.9 doesn't exist), Set volume and song to play in one command (H:volume L:track)
0x3C STAY Reserved
0x3D STAY Reserved
0x3E STAY Reserved
0x3F Send initialization parameters 0-0x0F (each bit represent one device of the low-four bits) Seems to be reporting which storage devices are currently online (see section 3.5.1)
0x40 Returns an error, request retransmission Probably this is a response to a command meaning Error Encountered.
0x41 Answer Probably this is a response to a query and just means Accepted.
0x42 Query Status See 3.4.10 (actually 3.5.2) - explains that this will return which storage is used and if it's playing, stopped or paused. The storage could also be 'SLEEP' to indicate sleeping?

Code to calculate the checksum from the Library supporting the miniplayer:

uint16_t DFRobotDFPlayerMini::calculateCheckSum(uint8_t *buffer){
  uint16_t sum = 0;
  for (int i=Stack_Version; i<Stack_CheckSum; i++) {
    sum += buffer[i];
  }
  return -sum;
}

but this doesn't seem to be needed.

Eyes

The 'Eyes' are connected over i2c. The daughterboard they are on features an STM8S103K3 (see http://www.st.com/en/microcontrollers/stm8s103-105.html ) and 2x HC595 (shift registers). Besides 35 predefined eye patterns, it is also possible to upload your own eye patterns.

The default eyes can be set as follows:

#include <Wire.h>
#define I2C_LED_ADDRESS 0b1100000
#define I2C_WRITE   0x00

uint8_t serial=0;

void setup(){
   Wire.begin(); // join i2c bus (address optional for master)
   defined_eyes(6,1);                                                                               
   delay(2000);
}
void loop(){
   defined_eyes(1,serial); 
   serial++;                                                                                        
   if(serial>=35) serial=0;                                                                         
   delay(250);                                                                                      
}


void defined_eyes(uint8_t color,uint8_t serial) {

   Wire.beginTransmission(I2C_LED_ADDRESS << 1 | I2C_WRITE); // transmit to device #4 
// (I suppose they mean 0x40, although eyes seem to also respond on 0xC0 which is what is defined above, and 0x20)
   Wire.write(color&0x07); // color bits: 1 blue, 2 green, 4 red 
   Wire.write(serial);  //preset eyes 0~34
   Wire.endTransmission();    // stop transmitting                                                  
}

void custom_eyes(uint8_t color, uint8_t eyeline[10]){
   uint8_t index;
   // right eye         left eye
   //1  2  3  4  5      25 24 23 22 21                                                              
   //6  7  8  9  10     20 19 18 17 16                                                              
   //11 12 13 14 15     15 14 13 12 11                                                              
   //16 17 18 19 20     10 9  8  7  6
   //21 22 23 24 25     5  4  3  2  1                                                               
   Wire.beginTransmission(I2C_LED_ADDRESS << 1 | I2C_WRITE); // transmit to device #4
   Wire.write(0x55);  // color 55=custom eyes follow 
   Wire.write(0xAA);  // ?? AA=color followed by 10 bytes, each defining one line
                      // other values cause eyes to light up in multiple colors, needs more work

   Wire.write(color&0x7);         //color 1-B,2-G,4-R of foreground
                                                                                                    
   for (index=0;index<10;index++) {
     Wire.write(eyeline[index]);
   }                                                                                                
   Wire.endTransmission();    // stop transmitting   


void example_eyes() {
   /* left eye of robot (right for onlooker) */
   Wire.write(0x1f); // bottom row, all bits lit
   Wire.write(0x1e); // 10 9 8 7 on, 6 off
   Wire.write(0x1c);
   Wire.write(0x18);
   Wire.write(0x10);
                                                                                                    
   /* right eye of robot (left for onlooker) */
   Wire.write(0x1f);  // top row all 5 bits lit
   Wire.write(0x0f);  // leds 6,7,8,9 on 10 off
   Wire.write(0x07);  // 11,12,13 on,  14,15 off                         
   Wire.write(0x03);                                                                                
   Wire.write(0x01);                                                                                
}                        
KITT-scanner / Cylon

This animates the eyes and adds some simple sound effects - currently the eye warble is not synchronized to the eye movement. The IR remote is used to trigger the well-known 'By your command'. For a KITT scanner may want to change to the proper woosh for a KITT, and replace the command sample with something like 'yes, Michael' :)

#include <Wire.h>
#include <SoftwareSerial.h>
#include <IRremote.h>

#define MTX (11)
#define MRX (4)

// Song number for "By your command"-sample
#define BYC (30)
// Song number for Warble
#define Warble (31)

#define IR_IN (7)

#define I2C_LED_ADDRESS 0x80
bool left=1;
decode_results results;
SoftwareSerial mp3(MRX,MTX);
IRrecv irrecv(IR_IN);
short command=0;

uint8_t eyes[10]={0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
void setup(){
  Wire.begin(); // join i2c bus (address optional for master)
  mp3Init();
  irrecv.enableIRIn(); // Start the receiver
  delay(2000);
  delay(100);
  mp3setVolume(10);//0~255, but 30 is already quite loud!
  delay(50);
  mp3player(Warble);
  delay(50);
  mp3repeat(2); // repeat once
  delay(50);
  mp3player(BYC);
  delay(3000);
  mp3setVolume(8);//0~255, but 30 is already quite loud!
  delay(50);
  mp3player(Warble);
  delay(50);
  mp3repeat(0); // repeat forever
  delay(50);
}
void lscroll_eyes(uint8_t * eyes ){
  uint8_t i;
  eyes[2]=(uint8_t)(eyes[2]<<1);
  eyes[7]=(uint8_t)(eyes[7]<<1);
}

void rscroll_eyes(uint8_t * eyes ){
  uint8_t i;
  eyes[2]=(uint8_t)(eyes[2]>>1);
  eyes[7]=(uint8_t)(eyes[7]>>1);
}

void custom_eyes(uint8_t color, uint8_t eyeline[10]){
  uint8_t index;
  Wire.beginTransmission(I2C_LED_ADDRESS); // 0x4 preshifted to 0x8 and lsb set to 0 for write
  Wire.write(0x55);  // register 55=?custom eyes
  Wire.write(0xAA); // ?? AA=color followed by 10 bytes, each defining one line
  Wire.write(color&0x7);         //color 1-B,2-G,4-R

  for (index=0;index<10;index++) {
    Wire.write(eyeline[index]);
  }
  Wire.endTransmission();    // stop transmitting
}

void loop(){

  // 1 2 4 8 10 '20' 10 8 4 2 1
  if (left) {lscroll_eyes(eyes);}
  else if (!left) {rscroll_eyes(eyes);}

  custom_eyes(4,eyes);

  if (eyes[7]==0x1) {left=1;}
  else if (eyes[2]==0x1) {left=1;}
  else if (left && eyes[2]>=0x10) {eyes[2]=0;eyes[7]=0x20;left=0;}
  else if (left && eyes[7]>=0x10) {eyes[7]=0;eyes[2]=0x20;left=0;}
  if (irrecv.decode(&results)){
    if (command) {
      command=0;
    } else{
      command=34; // adjust to match sample length
      mp3player(BYC);
      delay(50);
    }
    irrecv.resume();
  } else {
    if (command) {
      command--;
      if (!command){
        mp3player(Warble);
        delay(50);
        mp3repeat(0); // repeat forever
      } else {
        delay(80);
      }
    } else {
      delay(80);
    }
  }
}

void mp3Init()
{ 
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x0C, 0x00, 0x00, 0x00, 0xef};
  mp3.begin(9600);
  delay(100);
  mp3.write(buffer, 8);
  delay(100);
}
void mp3setVolume(byte vol)
{ 
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x06, 0x00, 0x00, vol, 0xef};
  mp3.write(buffer, 8);
}
void mp3player(byte data)
{ 
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x03, 0x00, 0x00, data, 0xef};
  mp3.write(buffer, 8);
}
void mp3repeat(byte data)
{ 
  uint8_t buffer[] = {0x7e, 0xff, 0x06, 0x08, 0x00, 0x00, data, 0xef};
  mp3.write(buffer, 8);
}
Conway's Game of Life

2 5x5 fields linked as a torus of 10x5. Change mapping of cells in the edge to make e.g. a front and a back side, or change to 8x8 and add 4 more faces and a bit more mapping to create an LED cube of 6 faces. Simple animation on the 2 eye matrices. This version doesn't actually do something with the remote control, but it was easy to add things like reset board, change color, introduce disturbance etc.

#include <Wire.h>
#include <IRremote.h>

#define IR_IN (7)
#define I2C_LED_ADDRESS 0x80

 // 5x5 matrix eye
#define CX (5)
#define CY (5)
// edge of 1 cell on each end
#define CXM (7)
#define CYM (7)

#define F (2) // 2 faces have I.

decode_results results;
IRrecv irrecv(IR_IN);

uint8_t eyes[10];

uint8_t board_a[F][CYM][CXM];
uint8_t board_b[F][CYM][CXM];


inline void clear(uint8_t board[][CYM][CXM]) {
  uint8_t i,j,k;
  for (k=0;k<F;k++) {
    for (j=0;j<CYM;j++) {
      for (i=0;i<CXM;i++) {
        board[k][j][i]=0;
      }
    }
  }
}

inline uint8_t neighbors(uint8_t s[][CYM][CXM], uint8_t k,uint8_t j,uint8_t i) {
  return s[k][j-1][i-1]+s[k][j-1][i]+s[k][j-1][i+1]+s[k][j][i-1]+s[k][j][i+1]+s[k][j+1][i-1]+s[k][j+1][i]+s[k][j+1][i+1];
}

inline void play(uint8_t s[][CYM][CXM], uint8_t d[][CYM][CXM]) {
  uint8_t i,j,k;
 // TODO: load edges from right faces hardcoded for 5x5 now
  for (i=1;i<=5;i++) {
    s[0][0][i]=s[0][5][i];
    s[0][6][i]=s[0][1][i];

    s[0][i][0]=s[1][i][5];
    s[0][i][6]=s[1][i][1];

    s[1][0][i]=s[1][5][i];
    s[1][6][i]=s[1][1][i];

    s[1][i][0]=s[0][i][5];
    s[1][i][6]=s[0][i][1];

  }
  // corners
    s[0][0][0]=s[1][5][5];
    s[0][6][0]=s[1][1][5];

    s[0][0][6]=s[1][5][1];
    s[0][6][6]=s[1][1][1];

    s[1][0][0]=s[0][5][5];
    s[1][6][0]=s[0][1][5];

    s[1][0][6]=s[0][5][1];
    s[1][6][6]=s[0][1][1];


 // update interior cells
  for (k=0;k<F;k++) {
    for (j=1;j<=CY;j++) {
      for (i=1;i<=CX;i++) {
        switch (neighbors(s,k,j,i)) {
          case 2: d[k][j][i]=s[k][j][i]; // remain
                  break;
          case 3: d[k][j][i]=1; // birth
                  break;
          default: d[k][j][i]=0; // death
        }
      }
    }
  }
}

inline uint8_t convert_l(uint8_t l[CXM]) {
return (l[5]|l[4]<<1|l[3]<<2|l[2]<<3|l[1]<<4);
}
inline uint8_t convert_r(uint8_t l[CXM]) {
return (l[1]|l[2]<<1|l[3]<<2|l[4]<<3|l[5]<<4);
}

inline uint8_t * board_to_eyes(uint8_t board[F][CYM][CXM]) {
  static uint8_t eyes[10]={0,0,0,0,0,0,0,0,0,0};
  eyes[0]=convert_l(board[1][5]);
  eyes[1]=convert_l(board[1][4]);
  eyes[2]=convert_l(board[1][3]);
  eyes[3]=convert_l(board[1][2]);
  eyes[4]=convert_l(board[1][1]);

  eyes[5]=convert_r(board[0][1]);
  eyes[6]=convert_r(board[0][2]);
  eyes[7]=convert_r(board[0][3]);
  eyes[8]=convert_r(board[0][4]);
  eyes[9]=convert_r(board[0][5]);

  return eyes;
}


void dump(uint8_t b[][CYM][CXM]) {
  uint8_t i,j,k;
  Serial.println("==");
  for (k=0;k<F;k++) {
    for (j=1;j<=CY;j++) {
      for (i=1;i<=CX;i++) {
        Serial.print(b[k][j][i]?'x':'o');
      }
      Serial.println("");
    } 
      Serial.println("==");
  }

}

void setup(){
  delay(2000);
  Serial.begin(9600);
  Serial.println("life");
  Wire.begin(); // join i2c bus (address optional for master)
  irrecv.enableIRIn(); // Start the receiver
  clear(board_a); // start with empty board a
  clear(board_b); // start with empty board b
  //some initial state:
  board_a[0][1][2]=1;
  board_a[0][2][3]=1;
  board_a[0][3][1]=1;
  board_a[0][3][2]=1;
  board_a[0][3][3]=1;
  board_a[1][1][2]=1;
  board_a[1][2][3]=1;
  board_a[1][3][1]=1;
  board_a[1][3][2]=1;
  board_a[1][3][3]=1;
  Serial.println("inited");
}

inline void custom_eyes(uint8_t color, uint8_t eyeline[10]){
  uint8_t index;
  // right eye          left eye
  //1  2  3  4  5      25 24 23 22 21
  //6  7  8  9  10     20 19 18 17 16
  //11 12 13 14 15     15 14 13 12 11
  //16 17 18 19 20     10 9  8  7  6
  //21 22 23 24 25     5  4  3  2  1

  Wire.beginTransmission(I2C_LED_ADDRESS); // 0x4 preshifted to 0x8 and lsb set to 0 for write
  Wire.write(0x55);  // register 55=?custom eyes
  Wire.write(0xAA); // ?? AA=color followed by 10 bytes, each defining one line
  Wire.write(color&0x7);         //color 1-B,2-G,4-R

  for (index=0;index<10;index++) {
    Wire.write(eyeline[index]);
  }
  Wire.endTransmission();    // stop transmitting
}

void loop(){
//  dump(board_a);
  custom_eyes(4,board_to_eyes(board_a));
  play(board_a,board_b);
  delay(50);
//  dump(board_b);
  custom_eyes(4,board_to_eyes(board_b));
  play(board_b,board_a);
  delay(50);

//TODO: add something to react to remote here, e.g. set some cells etc.
  if (irrecv.decode(&results)){
    irrecv.resume();
  } else {
  }
}

Countdown Timer

This sketch turns Vortex into an eggtimer to be used to keep track of how much time you have left when giving a talk, or before your food is ready. It displays the time left on the eyes, and can be controlled with a IR remote to (re)set the timer. LED colors are adjusted based on the time left, with vortex blinking red when you're out of time.

Below the timer is set up to count minutes, but you can of course change the number of milliseconds per 'tick' to count seconds or whatever. Adding things like rotating lights to indicate progress of time or playing sound effects if desired are left to the reader, this is more or less a demo of using the eyes as a digit display.

Because the two eyes are rotated 180 degrees compared to each other, there is code to swap bit-patterns (for the full 5 pixels per row, even if the digits here only use 3 per row). The font is rotated at run-time in setup(), so it's easier to tweak the font.

Make sure to restart Vortex in time for the 55 day limit on the millis() roll-over not to affect things, or add code to detect and handle the rollover.

The code to work with a remote is set up for a specific remote controller (RC 240 in TV mode), so you probably have to adjust this bit to work with the controller you intend to use.

#include <Wire.h>                                                                                   
#include <IRremote.h>                                                                               
#include <FastLED.h>                                                                                
//IR receiver pin                                                                                   
#define IR_IN  (7)                                                                                  
#define I2C_LED_ADDRESS 0b100000                                                                    
#define I2C_WRITE   0x00                                                                            
// How many leds are in the strip?                                                                  
#define NUM_LEDS 12                                                                                 
// Data pin that led data will be written out over                                                  
#define DATA_PIN 13                                                                                 
CRGB leds[NUM_LEDS];                                                                           
// how many milliseconds per count (1000=count seconds, 60000 count minutes)                        
unsigned long MperC=60000;                                                                           
                                                                                                    
unsigned long start;                                                                                
short countdown;                                                                                    
short displayed;                                                                                    
uint8_t state=0;                                                                                    
unsigned long m_elapsed;                                                                            
short c_elapsed;                                                                                    
unsigned long long current=0xffffffff;                                                              
unsigned long long last=0xffffffff;                                                                 
                                                                                                    
IRrecv irrecv(IR_IN);                                                                               
decode_results results;                                                                             
                         
uint8_t eyes[10]={0,0,0,0,0,0,0,0,0,0};                                                             
                                                                                                    
uint8_t left_digit[10][5]={                                                                         
    {0x04,0x0A,0x0A,0x0A,0x04}, //0                                                                 
    {0x0E,0x04,0x04,0x0C,0x04}, //1                                                                 
    {0x0E,0x08,0x04,0x02,0x0C}, //2                                                                 
    {0x0C,0x02,0x0C,0x02,0x0C}, //3                                                                 
    {0x02,0x02,0x0E,0x0A,0x0A}, //4                                                                 
    {0x0E,0x02,0x0C,0x08,0x0E}, //5                                                                 
    {0x04,0x0A,0x0C,0x08,0x04}, //6                                                                 
    {0x04,0x04,0x02,0x02,0x0C}, //7                                                                 
    {0x04,0x0A,0x04,0x0A,0x04}, //8                                                                 
    {0x04,0x02,0x06,0x0A,0x04}  //9                                                                 
};                                                                                                  
uint8_t right_digit[10][5];                                                                         
uint8_t swap(uint8_t l) {                                                                           
    switch (l&0x1f){                                                                                
        case 0x0: return 0;                                                                         
        case 0x01: return 0x10;                                                                     
        case 0x02: return 0x08;                                                                     
        case 0x03: return 0x18;                                                                     
        case 0x04: return 0x04;                                                                     
        case 0x05: return 0x14;                                                                     
        case 0x06: return 0x0C;                                                                     
        case 0x07: return 0x1C;                                                                     
        case 0x08: return 0x02;                                                                     
        case 0x09: return 0x12;                                                                     
        case 0x0a: return 0x0A;                                                                     
        case 0x0b: return 0x1A;                                                                     
        case 0x0c: return 0x06;                                                                     
        case 0x0d: return 0x16;                                                                     
        case 0x0e: return 0x0E;                                                                     
        case 0x0f: return 0x1E;                                                                     
        case 0x10: return 0x01;                                                                     
        case 0x11: return 0x11;                                                                     
        case 0x12: return 0x09;                                                                     
        case 0x13: return 0x19;                                                                     
        case 0x14: return 0x05;                                                                     
        case 0x15: return 0x15;                                                                     
        case 0x16: return 0x0D;                                                                     
        case 0x17: return 0x1d;                                                                     
        case 0x18: return 0x03;                                                                     
        case 0x19: return 0x13;                                                                     
        case 0x1a: return 0x0B;                                                                     
        case 0x1b: return 0x1B;                                                                     
        case 0x1c: return 0x07;                                                                     
        case 0x1d: return 0x17;                                                                     
        case 0x1e: return 0x0F;                                                                     
        case 0x1f: return 0x1F;                                                                     
        default: return 0;                                                                          
    }                                                                                               
}                                                                                                   
void setup(){                                                                                       
    char i,j;                                                                                       
    delay(2000);                                                                                    
    FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);                                         
    for (i=0;i<10;i++){                                                                             
        for (j=0;j<5;j++) {                                                                         
            right_digit[i][j]=swap(left_digit[i][4-j]);                                             
        }                                                                                           
    }                                                                                               
    irrecv.enableIRIn();                                                                            
    Wire.begin(); // join i2c bus (address optional for master)                                     
    FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);                                         
    for (i=0;i<12;i++){                                                                             
        leds[i]=CRGB::Green;                                                                        
    }                                                                                               
    FastLED.show();                                                                                 
    start=millis();                                                                                 
    displayed=100;                                                                                  
    countdown=99;                                                                                   
    state=0;                                                                                        
}
void custom_eyes(uint8_t color, uint8_t eyeline[10]){                                               
   uint8_t index;                                                                                   
   // right eye         left eye                                                                    
   //1  2  3  4  5      25 24 23 22 21                                                              
   //6  7  8  9  10     20 19 18 17 16                                                              
   //11 12 13 14 15     15 14 13 12 11                                                              
   //16 17 18 19 20     10 9  8  7  6                                                               
   //21 22 23 24 25     5  4  3  2  1                                                               
                                                                                                    
   Wire.beginTransmission(I2C_LED_ADDRESS << 1 | I2C_WRITE); // transmit to device #4               
   Wire.write(0x55);  // color 55=custom eyes follow                                                
   Wire.write(0xAA); // ?? AA=color followed by 10 bytes, each defining one line                    
   Wire.write(color&0x7);         //color 1-B,2-G,4-R                                               
                                                                                                    
   for (index=0;index<10;index++) {                                                                 
     Wire.write(eyeline[index]);                                                                    
   }                                                                                                
   Wire.endTransmission();    // stop transmitting                                                  
   // A9 = some sort of multicolor mode                                                             
   // AA = one color byte, then 10 bytes defining the 2 eyes.                                       
   // AB = some sort of multicolor mode                                                             
}                                                                                                   
void update_display(short displayed) {                                                              
    byte h=displayed/10;                                                                            
    byte l=displayed%10;                                                                            
    eyes[0]=left_digit[l][0];                                                                       
    eyes[1]=left_digit[l][1];                                                                       
    eyes[2]=left_digit[l][2];                                                                       
    eyes[3]=left_digit[l][3];                                                                       
    eyes[4]=left_digit[l][4];                                                                       
    eyes[5]=right_digit[h][0];                                                                      
    eyes[6]=right_digit[h][1];                                                                      
    eyes[7]=right_digit[h][2];                                                                      
    eyes[8]=right_digit[h][3];                                                                      
    eyes[9]=right_digit[h][4];                                                                      
    if (displayed<5) {                                                                              
        custom_eyes(4,eyes);                                                                        
    } else {                                                                                        
        custom_eyes(2,eyes);                                                                        
    }                                                                                               
}                                                                                                   
                                                                                                    
void update_leds(short displayed) {                                                                 
    char i;                                                                                         
    if (displayed>5) {                                                                              
        for (i=0;i<12;i++){                                                                         
            leds[i]=CRGB(0,displayed*2,0);                                                          
        }                                                                                           
    } else if (displayed>0) {                                                                       
        for (i=0;i<12;i++){                                                                         
            leds[i]=0xff9900;                                                                       
        }                                                                                           
    } else if (state==1) {                                                                          
        for (i=0;i<6;i++) {                                                                         
            leds[i]=CRGB::Red;                                                                      
            leds[i+6]=CRGB::Black;                                                                  
        }                                                                                           
        state=0;                                                                                    
    } else if (state==0) {                                                                          
        for (i=0;i<6;i++) {                                                                         
            leds[i+6]=CRGB::Red;                                                                    
            leds[i]=CRGB::Black;                                                                    
        }                                                                                           
        state=1;                                                                                    
    }                                                                                               
    FastLED.show();                                                                                 
}
void loop(){                                                                                        
    unsigned long m_elapsed=millis()-start;                                                         
    // FIXME handle overflow of millis.                                                             
    short c_elapsed=(m_elapsed/MperC);                                                              
    if (displayed>0) {                                                                              
        if (countdown-c_elapsed!=displayed) {                                                       
            displayed=countdown-c_elapsed;                                                          
            displayed=displayed<0?0:displayed;                                                      
            update_display(displayed);                                                              
        }                                                                                           
    }                                                                                               
    update_leds(displayed);                                                                         
    //TODO check IR commands                                                                        
    if (irrecv.decode(&results)) {                                                                  
        current=results.value;                                                                      
        if (current!=last) {                                                                        
            last=current; // store current key to be able to recognize repeats                      
            current=current&0xfffff7ff; // filter out repeat bit. This is for RC5.                  

            if (current==0xa) { // 'clear' on my particular remote                                  
                countdown=0;                                                                        
            } else if (current==0xf) { // 'memo'                                                    
                start=millis(); // reference time is now                                            
                displayed=99;  // restarts timer with current countdown setting                     
            } else if (current==0x0) { // 'digit 0', not all remotes might be this regular          
                digit(0);                                                                           
            } else if (current==0x1) {                                                              
                digit(1);                                                                           
            } else if (current==0x2) {                                                              
                digit(2);                                                                           
            } else if (current==0x3) {                                                              
                digit(3);                                                                           
            } else if (current==0x4) {                                                              
                digit(4);                                                                           
            } else if (current==0x5) {                                                              
                digit(5);                                                                           
            } else if (current==0x6) {                                                              
                digit(6);                                                                           
            } else if (current==0x7) {                                                              
                digit(7);                                                                           
            } else if (current==0x8) {                                                              
                digit(8);                                                                           
            } else if (current==0x9) {                                                              
                digit(9);                                                                           
            } else if (current==0x0) { // TODO add pause, up/down by 1                              
            }                                                                                       
        }                                                                                           
        irrecv.resume(); // Receive the next value                                                  
    }                                                                                               

    delay(500);                                                                                     
}                                                                                                   
void digit(byte d) {                                                                                
    displayed=0; // stops timer updates to display                                                  
    countdown=(countdown%10)*10+d;                                                                  
    update_display(countdown);                                                                      
}

i2c

There might be more stuff connected to the i2c already, I plan to do some tests to find out. Would be nice if e.g. the flash of the mp3 player or some other expansion was already there.

Regardless of what's there already, Vortex seems to be designed to be extended over i2c, and could be a nice platform to do some experiments/demo's with.

Here's a simple scanning sketch, looks like only 0x40 has something connected, and that would be the eye-display.

#include <Wire.h>
 
 
void setup()
{
  Wire.begin();
 
  Serial.begin(9600);
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}
 
 
void loop()
{
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning...");
 
  nDevices = 0;
  for(address = 0; address < 128; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.print("  (");
      Serial.print(address,DEC);
      Serial.println("d)  !");
 
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.print("  (");
      Serial.print(address,DEC);
      Serial.println("d)  !");
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
}

Bluetooth

It seems the Bluetooth functionality is the same as for the "Bluno" and thus the information at https://www.dfrobot.com/wiki/index.php/Bluno_SKU:DFR0267#Wireless_Programming_via_BLE applies.

The AT commands seem to work, up to a point, but looks like it is a somewhat variant version of the firmware as AT+VERSION command doesn't work. Updating program seem not available for Linux.

Booting the Vortex with the 'boot' button (near UL7, Vortex bottom-right-back RGB led.), two leds blink, and linux detects the Vortex over USB as:

[230656.359259] usb 1-1.1: new full-speed USB device number 103 using xhci_hcd
[230656.488502] usb 1-1.1: New USB device found, idVendor=2341, idProduct=0043
[230656.488508] usb 1-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[230656.488511] usb 1-1.1: Product: DFRobot Boot CDC
[230656.488513] usb 1-1.1: Manufacturer: DFRobot BLUno Boot
[230656.490491] cdc_acm 1-1.1:1.0: ttyACM0: USB ACM device

Similarly the little BLE dongle comes up as:

[232626.931574] usb 1-1.3: new full-speed USB device number 108 using xhci_hcd
[232627.254357] usb 1-1.3: New USB device found, idVendor=2341, idProduct=0043
[232627.254362] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[232627.265758] cdc_acm 1-1.3:1.0: ttyACM0: USB ACM device     

If Vortex pairs with the dongle, it effectiely gains an additional, wireless, serial link to a different port on the host. It's connected to the same Serial port on the internal Arduino. Make sure to adjust speed setting to what is used in the currently running sketch, the default 115200 won't work if Serial.begin(9600) is already called..


Using AT commands it should be possible to set up the link connect to other Vortex robots too.

Need to do some experiments to see if it is possible to connect to a generic Bluetooth 4.x dongle too.

Expansion

hole arangement seen from top, vortex facing left or right

There are 2 sets of 4 positions for screws/bolts at the top of the vortex, around the battery compartment, obviously to be used to put an expansion unit on top of Vortex, which could be linked to the i2c bus.

Dimensions

The bigger set of holes are 65 mm apart from left to right and 55 from front to back (center to center). These seem to be meant for 3 mm machine screws

The smaller set of holes are 2.5" apart from left to right and 2.75" from front to back (center to center). These seem to be meant for small screws biting into the plastic.

The 4 pin expansion port
Q: Does Vortex support sensor module expansion?
A: Vortex has a “Maker mode” where you can connect sensors via its IIC/TWI interface.

It would seem the only logical place for this is the 4 pin connector in the back. Looking at the connector from the back of Vortex:

 ____
|1234|
 -__-

These pins seem to be connected to the rainbow cable coming up from the main PCB as follows:

Vortex expansion port
Pin Function Color
1 SDA? ~15kOhm to Blue
2 SCL? Blue
3 Gnd? Black
4 Vcc? Red

It would make sense for Red to be 5V, Black to be Gnd, and the other two SCL and SDA of the i2c bus. The ~15kOhm between what I suspect to be SCL and SDA would then be two ~7.5kOhm to some common 5 V point that is buffered from the 'Red' cable.

I did some further testing with a known good i2c device connected to the expansion port, but this just hung the i2c scanner or crashed Vortex depending on how I connected the port to the little display. Needs more work...

Both pin 3 and 4 seem to be at +5V to the usb ground