project:brmpaw:sketch
Differences
This shows you the differences between two versions of the page.
project:brmpaw:sketch [2011/03/04 21:26] – created pasky | project:brmpaw:sketch [2011/03/04 21:28] (current) – pasky | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | /* 1:1 with EEPROM contents */ | ||
+ | struct config { | ||
+ | byte is_initialized[3]; | ||
+ | |||
+ | /* Length of periods when motor is on/off; in 10ms units. */ | ||
+ | /* Well, why duration_{on, | ||
+ | | ||
+ | I found out, an user's brain get used very quickly (in about a minute) on a periodic | ||
+ | | ||
+ | the feeling is really weird. | ||
+ | Of course it's cool to test it in a laboratory (I suggest testing with ECG attached; | ||
+ | I bet there would be at least 5/min rise), where you control the anklet from a computer. | ||
+ | But it's absolutely impossible to go with it in the real life. Yes, you could be | ||
+ | (and probably are) more psychically resistent than me, but I think it would be | ||
+ | | ||
+ | And, of course, we save 28 B of our little memory ;-) | ||
+ | */ | ||
+ | /* 3 */ byte duration_off; | ||
+ | /* 4 */ byte duration_on; | ||
+ | |||
+ | /* It's good time to mentoin physical construction. | ||
+ | | ||
+ | |||
+ | And about the intensity. | ||
+ | 0 should be off (to conserver power). | ||
+ | 1 should be the lowest perceptable boundary. | ||
+ | 255 should be the boundary where the motors start burning. | ||
+ | But currently this is broken, we have to do some measurements on volunteers | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | */ | ||
+ | /* 5 */ byte pwm_min; // PWM duty cycle for intensity 1 | ||
+ | /* 6 */ byte pwm_max; // PWM duty cycle for intensity 255 | ||
+ | |||
+ | /* Bearing with 2\deg precision. It denotes which angle relative to current | ||
+ | * heading will the motors " | ||
+ | * the motor currently to the north will keep vibrating, while bearing==180/ | ||
+ | * will mean that the south motor will keep vibrating. */ | ||
+ | /* 7 */ byte bearing; | ||
+ | |||
+ | /* After fadeout_start*10 seconds, start linearly reducing intensity; after | ||
+ | * another fadeout_length*10 seconds, stop vibrating altogether. */ | ||
+ | /* 8 */ byte fadeout_start; | ||
+ | /* 9 */ byte fadeout_length; | ||
+ | /* Angle changes under max_jitter are not considered direction changes for gradual | ||
+ | * vibration fade-out. */ | ||
+ | /* 10 */ byte max_jitter; | ||
+ | |||
+ | /* 11-18 */ byte motor_intensity[8]; | ||
+ | |||
+ | /* 19-26 */ byte motor_angle_start[8]; | ||
+ | }; | ||
+ | |||
+ | struct config config; | ||
+ | const PROGMEM struct config default_config = { | ||
+ | / | ||
+ | / | ||
+ | / | ||
+ | /*.pwm_min =*/ 30, | ||
+ | /*.pwm_max =*/ 160, | ||
+ | /*.bearing =*/ 0, | ||
+ | / | ||
+ | / | ||
+ | / | ||
+ | / | ||
+ | // 45\deg section for each motor; motor 0 is around angle 0 | ||
+ | / | ||
+ | }; | ||
+ | |||
+ | |||
+ | const byte ShiftEnable = 4; // Every time you use variable instead of constant and int instead of byte, God kills a kitten and wastes 2 B of memory. | ||
+ | const byte ShiftSerIn = 2; | ||
+ | const byte ShiftClear = 3; | ||
+ | const byte ShiftClock = 5; | ||
+ | const byte ShiftCommit = 6; | ||
+ | |||
+ | byte heading = 0; | ||
+ | unsigned long CurrTime = 0; // Hmm, my first 4 B of memory I've allocated out of that 1 KiB. | ||
+ | unsigned long NextEnvelopeSwitch = 0; | ||
+ | boolean EnvelopeStatus = false; | ||
+ | byte CurrMotor = 0; | ||
+ | boolean ExternalControl = false; | ||
+ | byte pwm; | ||
+ | |||
+ | byte ref_heading = 0; | ||
+ | unsigned long ref_heading_time = 0; | ||
+ | |||
+ | boolean KeyboardControl = false; | ||
+ | |||
+ | byte packet = 0; // Packet to send to the shift register. | ||
+ | byte ManualIntensity = 60; | ||
+ | byte FadeoutIntensity = 255; | ||
+ | |||
+ | |||
+ | void config_defaults(void) { | ||
+ | memcpy_P(& | ||
+ | } | ||
+ | |||
+ | void config_load(void) { | ||
+ | eeprom_read_block(& | ||
+ | if (memcmp(config.is_initialized, | ||
+ | config_defaults(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void config_save(void) { | ||
+ | eeprom_write_block(& | ||
+ | } | ||
+ | |||
+ | byte *config_as_buf(struct config *c) | ||
+ | { | ||
+ | union config_buf { | ||
+ | struct config c; | ||
+ | byte b[sizeof(struct config)]; | ||
+ | }; | ||
+ | return ((union config_buf*) c)->b; | ||
+ | } | ||
+ | |||
+ | |||
+ | void setup() { | ||
+ | pinMode(ShiftEnable, | ||
+ | pinMode(ShiftSerIn, | ||
+ | pinMode(ShiftClear, | ||
+ | pinMode(ShiftClock, | ||
+ | pinMode(ShiftCommit, | ||
+ | |||
+ | config_load(); | ||
+ | |||
+ | digitalWrite(ShiftEnable, | ||
+ | digitalWrite(ShiftClear, | ||
+ | digitalWrite(ShiftCommit, | ||
+ | digitalWrite(ShiftClock, | ||
+ | digitalWrite(ShiftClear, | ||
+ | |||
+ | Serial.begin(9600); | ||
+ | Serial.println(" | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | // TODO ReadCompass | ||
+ | CurrTime = millis(); | ||
+ | FadeIntensity(); | ||
+ | ChangeMotor(); | ||
+ | ManageEnvelope(); | ||
+ | ServeConsole(); | ||
+ | } | ||
+ | |||
+ | |||
+ | void FadeIntensity() { | ||
+ | FadeoutIntensity = 255; | ||
+ | if (abs(ref_heading - heading) > config.max_jitter) { | ||
+ | ref_heading = heading; | ||
+ | ref_heading_time = CurrTime; | ||
+ | return; | ||
+ | } | ||
+ | unsigned long fadeout_start = ((unsigned long) config.fadeout_start) * (1000 * 10); | ||
+ | unsigned long fadeout_length = ((unsigned long) config.fadeout_length) * (1000 * 10); | ||
+ | long fadeout_time_ofs = (CurrTime - ref_heading_time) - fadeout_start; | ||
+ | if (fadeout_time_ofs < 0) | ||
+ | return; | ||
+ | if (fadeout_time_ofs > fadeout_length) { | ||
+ | FadeoutIntensity = 0; | ||
+ | return; | ||
+ | } | ||
+ | FadeoutIntensity = ((unsigned long) config.motor_intensity[CurrMotor]) * (fadeout_length - fadeout_time_ofs) / fadeout_length; | ||
+ | // Serial.println(FadeoutIntensity, | ||
+ | } | ||
+ | |||
+ | void ManageEnvelope() { | ||
+ | /* Yo we herd you like rectangle wawes, so we put rectangle wave into a rectangle envelope, | ||
+ | so u can generate rectangle wave while u are generating rectangle wave! */ | ||
+ | |||
+ | if (NextEnvelopeSwitch <= CurrTime) { | ||
+ | // Yea, we have to switch the state! | ||
+ | if (EnvelopeStatus) { | ||
+ | // Motor is ON, so we will turn it OFF | ||
+ | NextEnvelopeSwitch = CurrTime + config.duration_off*10; | ||
+ | } | ||
+ | else { | ||
+ | // Motor is OFF, so we will turn it ON, what's the problem?! (--Moss, The IT Crowd) | ||
+ | NextEnvelopeSwitch = CurrTime + config.duration_on*10; | ||
+ | } | ||
+ | EnvelopeStatus = !EnvelopeStatus; | ||
+ | if (EnvelopeStatus) { | ||
+ | if(!ExternalControl) { | ||
+ | byte intensity = config.motor_intensity[CurrMotor]; | ||
+ | if (FadeoutIntensity < intensity) intensity = FadeoutIntensity; | ||
+ | pwm = ComputePWM(intensity); | ||
+ | } | ||
+ | else { | ||
+ | pwm = ComputePWM(ManualIntensity); | ||
+ | } | ||
+ | // | ||
+ | analogWrite(ShiftEnable, | ||
+ | } | ||
+ | else { | ||
+ | analogWrite(ShiftEnable, | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void ChangeMotor() { | ||
+ | if(!ExternalControl) { | ||
+ | // Select motor | ||
+ | byte i=0; | ||
+ | CurrMotor = 0; | ||
+ | byte motor_angle = (config.bearing + heading) % (360/2); | ||
+ | for (i=1; i<8; i++) { | ||
+ | if (config.motor_angle_start[i] > motor_angle) { | ||
+ | CurrMotor = i - 1; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | // Compute binary code | ||
+ | packet = 0; | ||
+ | bitWrite(packet, | ||
+ | } | ||
+ | // Pipe it into buffer | ||
+ | shiftOut(ShiftSerIn, | ||
+ | // And commit | ||
+ | delayMicroseconds(100); | ||
+ | digitalWrite(ShiftCommit, | ||
+ | delayMicroseconds(100); | ||
+ | digitalWrite(ShiftCommit, | ||
+ | } | ||
+ | |||
+ | byte ComputePWM(byte Intensity) { | ||
+ | byte DutyCycle; | ||
+ | DutyCycle = Intensity ? map(Intensity, | ||
+ | return DutyCycle; | ||
+ | } | ||
+ | |||
+ | void ServeConsole() { | ||
+ | static char cmd[16]; | ||
+ | static int cmdlen = 0; | ||
+ | |||
+ | while(Serial.available()) { // Read chars from buffer. | ||
+ | byte c = Serial.read(); | ||
+ | if (KeyboardControl) { | ||
+ | KeyboardSwitch(c); | ||
+ | return; | ||
+ | } | ||
+ | if (c == 10 || c == 13 || c==64) { | ||
+ | ExecCommand(cmd); | ||
+ | cmd[cmdlen = 0] = 0; | ||
+ | return; | ||
+ | } | ||
+ | if (c == ' | ||
+ | cmd[cmdlen = 0] = 0; | ||
+ | Serial.println(" | ||
+ | return; | ||
+ | } | ||
+ | cmd[cmdlen++] = c; | ||
+ | cmd[cmdlen] = 0; | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | void ExecCommand(char *cmd) { | ||
+ | switch (cmd[0]) { | ||
+ | case ' | ||
+ | ExternalControl = true; | ||
+ | KeyboardControl = true; | ||
+ | Serial.println(" | ||
+ | break; | ||
+ | case ' | ||
+ | cmd++; while (isspace(*cmd)) cmd++; | ||
+ | config.bearing = atoi(cmd) / 2; | ||
+ | Serial.print(" | ||
+ | Serial.println(config.bearing * 2, DEC); | ||
+ | break; | ||
+ | case ' | ||
+ | { | ||
+ | byte address, value; | ||
+ | cmd++; while (isspace(*cmd)) cmd++; | ||
+ | address = strtol(cmd, &cmd, 10); | ||
+ | while (isspace(*cmd)) cmd++; | ||
+ | value = strtol(cmd, &cmd, 10); | ||
+ | |||
+ | byte *buf = config_as_buf(& | ||
+ | buf[address] = value; | ||
+ | Serial.print(" | ||
+ | Serial.print(address, | ||
+ | Serial.print(" | ||
+ | Serial.println(value, | ||
+ | } | ||
+ | break; | ||
+ | case ' | ||
+ | { | ||
+ | byte *buf = config_as_buf(& | ||
+ | for (byte address = 0; address < sizeof(config); | ||
+ | Serial.print(address, | ||
+ | Serial.print(": | ||
+ | Serial.println(buf[address], | ||
+ | } | ||
+ | } | ||
+ | break; | ||
+ | case ' | ||
+ | config_save(); | ||
+ | Serial.println(" | ||
+ | break; | ||
+ | case ' | ||
+ | config_defaults(); | ||
+ | Serial.println(" | ||
+ | break; | ||
+ | default: | ||
+ | Serial.println(" | ||
+ | break; | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | |||
+ | void KeyboardSwitch(byte c) { | ||
+ | switch (c) { | ||
+ | case ' | ||
+ | KeyboardControl = false; | ||
+ | ExternalControl = false; | ||
+ | Serial.println(" | ||
+ | break; | ||
+ | case ' | ||
+ | ManualIntensity+=5; | ||
+ | break; | ||
+ | case ' | ||
+ | ManualIntensity-=5; | ||
+ | break; | ||
+ | case ' | ||
+ | config.duration_off+=1; | ||
+ | break; | ||
+ | case ' | ||
+ | config.duration_off-=1; | ||
+ | break; | ||
+ | case ' | ||
+ | config.duration_on+=1; | ||
+ | break; | ||
+ | case ' | ||
+ | config.duration_on-=1; | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(0); | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(1); | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(2); | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(3); | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(4); | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(5); | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(6); | ||
+ | break; | ||
+ | case ' | ||
+ | SwitchBit(7); | ||
+ | break; | ||
+ | default: | ||
+ | Serial.println(" | ||
+ | break; | ||
+ | } | ||
+ | Serial.print(" | ||
+ | Serial.print(ManualIntensity, | ||
+ | Serial.print(" | ||
+ | Serial.print(pwm, | ||
+ | Serial.print(" | ||
+ | Serial.print(config.duration_off*10, | ||
+ | Serial.print(" | ||
+ | Serial.print(config.duration_on*10, | ||
+ | Serial.print(" | ||
+ | Serial.println(packet, | ||
+ | } | ||
+ | |||
+ | void SwitchBit (byte pos) { | ||
+ | if(bitRead(packet, | ||
+ | bitWrite(packet, | ||
+ | } | ||
+ | else { | ||
+ | bitWrite(packet, | ||
+ | } | ||
+ | } | ||
+ | </ |