Avant la description, le fonctionnement général est à voir ici :

|
Le circuitA base d'atmega328 comme d'habitude.RésultatLe PCB : double face assez simple. Consommation : +12V = 25 mA ; -12V = 2 mA |
The circuitAtmega328 based as usual. ResultThe PCB: 2 simple layers. |


Le code est divisé en trois sections :
/* Trigger to Gate to Trigger
V 0.1 : init
V 0.2 : 2 leds per channel (TTG & mode)
V 0.3 : EEPROM saving of presets
V 0.4 : 2 more modes : random gate & bouncing ball
V 0.5 : minor modifications + english translation
V 0.6 : direct port manipulations instead of digitalRead and digitalWrite
2020 LC www.la-roue-tourne.fr/index.php/modulaire
-------------------------------------------------------
D0: port D bit 0
D1: port D bit 1
D2: port D bit 2 IN 1
D3: port D bit 3 IN 2
D4: port D bit 4 OUT 1
D5: port D bit 5 OUT 2
D6: port D bit 6 SWITCH RANGE 1
D7: port D bit 7 SWITCH RANGE 2
D8: port B bit 0 Led 1
D9: port B bit 1 Led 2
D10: port B bit 2 Led 3
D11: port B bit 3 Led 4
D12: port B bit 4 BP1 with built-in debouncer
D13: port B bit 5 BP2 with built-in debouncer
A0: port C bit 0 POT 1
A1: port C bit 1 CV 1
A2: port C bit 2 POT 2
A3: port C bit 3 CV 2
A4: port C bit 4
A5: port C bit 5
*/
// modes
#define TTG 0 // trigger to gate with 2 sub modes
#define GTT 1 // gate to trigger with 2 sub modes
#define RND 2 // random ou bouncing with 2 sub modes
/* set FIRST_RUN to true for the first time then set it to false
as it is necessery to have a good EEPROM data set
*/
#define FIRST_RUN false
// --------------------------------------------------- channels
struct {
unsigned long startTime;
int potValue, jackValue;
int mode, subMode;
int upDuration;
int nextBounce;
int pinInput, pinOutput, pinRange, pinLED_mode, pinLED_subMode, pinPushButton;
int range;
int oldState, oldPushButton;
boolean gateUp, bounceRunning;
} channel[2];
// --------------------------------------------------- potentiometer & switch reading
int potCounter;
unsigned long potDate, buttonDate;
// --------------------------------------------------- blinking LED (RND mode only)
int stateLed;
void setup() {
int i;
randomSeed(analogRead(A0) + analogRead(A2));
channel[0].pinInput = 2;
channel[1].pinInput = 3;
channel[0].pinOutput = 4;
channel[1].pinOutput = 5;
channel[0].pinRange = 6;
channel[1].pinRange = 7;
channel[0].pinLED_mode = 8;
channel[1].pinLED_mode = 10;
channel[0].pinLED_subMode = 9;
channel[1].pinLED_subMode = 11;
channel[0].pinPushButton = 12;
channel[1].pinPushButton = 13;
for (i = 0; i < 2; i++) {
pinMode(channel[i].pinInput, INPUT_PULLUP);
pinMode(channel[i].pinRange, INPUT_PULLUP);
pinMode(channel[i].pinOutput, OUTPUT);
pinMode(channel[i].pinLED_mode, OUTPUT);
pinMode(channel[i].pinLED_subMode, OUTPUT);
pinMode(channel[i].pinPushButton, INPUT_PULLUP);
channel[i].bounceRunning = false;
stateLed = 0;
gateDown(i);
}
// before the 1st run, to have a good EEPROM data
if (FIRST_RUN) {
for (i = 0; i < 2; i++) {
channel[i].mode = 0;
channel[i].subMode = 0;
}
saveEEPROM();
} else {
loadEEPROM();
}
initPot();
updateLED();
buttonDate = 0;
}
void loop() {
unsigned long now = millis();
int state[2];
int i;
// ----------------------------------------- gates
state[0] = bitRead(PORTD, 2);
state[1] = bitRead(PORTD, 3);
for (i = 0; i < 2; i++) {
if (state[i] != channel[i].oldState) {
channel[i].oldState = state[i];
switch (channel[i].mode) {
case TTG :
if ((state == HIGH) && ((channel[i].subMode == 1) || (!channel[i].gateUp))) {
gateUp(i, now);
}
break;
case GTT :
if (channel[i].subMode == 0) {
if (state == HIGH) {
gateUp(i, now);
}
} else {
gateUp(i, now);
}
break;
case RND :
if (state == HIGH) {
if (channel[i].subMode == 0) {
if (random(2046) < channel[i].upDuration) {
gateUp(i, now);
}
} else {
if (!channel[i].bounceRunning) {
channel[i].bounceRunning = true;
gateUp(i, now);
channel[i].nextBounce = channel[i].upDuration * 0.9;
}
}
}
break;
}
}
}
// ----------------------------------------- bounces & triggers' end
for (i = 0; i < 2; i++) {
if (channel[i].mode != RND) {
if (now - channel[i].startTime >= channel[i].upDuration) {
gateDown(i);
}
} else {
// ----------------------------- these are fixed triggers (30 ms)
if (now - channel[i].startTime >= 30) {
gateDown(i);
}
// ----------------------------- we are bouncing
if (channel[i].bounceRunning) {
if (now - channel[i].startTime >= channel[i].nextBounce) {
gateUp(i, now);
channel[i].nextBounce *= 0.9;
if (channel[i].nextBounce < 30) {
channel[i].bounceRunning = false;
}
}
}
}
}
// ----------------------------------------- switches & pots & LEDs
if (now - potDate > 101) {
potDate = now;
stateLed = 1 - stateLed;
updateLED();
potCounter ++;
if (potCounter > 5) {
potCounter = 0;
}
switch (potCounter) {
case 0:
channel[0].potValue = analogRead(A0);
updateDuration(0);
break;
case 1:
channel[0].jackValue = analogRead(A1);
updateDuration(0);
break;
case 2:
channel[1].potValue = analogRead(A2);
updateDuration(1);
break;
case 3:
channel[1].jackValue = analogRead(A3);
updateDuration(1);
break;
case 4:
channel[0].range = bitRead(PORTD, 6);
updateDuration(0);
break;
case 5:
channel[1].range = bitRead(PORTD, 7);
updateDuration(1);
break;
}
}
// ----------------------------------------- push buttons
if (now - buttonDate > 127) {
buttonDate = now;
state[0] = bitRead(PORTB, 4);
state[1] = bitRead(PORTB, 5);
for (i = 0; i < 2; i++) {
if (state[i] != channel[i].oldPushButton) {
channel[i].oldPushButton = state[i];
// the button is on
if (state == HIGH) {
channel[i].subMode ++;
if (channel[i].subMode > 1) {
channel[i].subMode = 0;
channel[i].mode ++;
if (channel[i].mode > 2) {
channel[i].mode = 0;
}
}
channel[i].bounceRunning = false;
saveEEPROM();
}
}
}
}
}
#include EEPROM.h
/*
* IMPORTANT :
*
* mode & subMode are INTs (2-byte long)
*
*/
void saveEEPROM() {
int adress = 0;
for (int i = 0; i < 2; i++) {
EEPROM.put(adress, channel[i].mode);
adress += 2;
EEPROM.put(adress, channel[i].subMode);
adress += 2;
}
}
void loadEEPROM() {
int adress = 0;
for (int i = 0; i < 2; i++) {
EEPROM.get(adress, channel[i].mode);
adress += 2;
EEPROM.get(adress, channel[i].subMode);
adress += 2;
}
}
void gateUp(int numGate, unsigned long dateGateUp) {
if (numGate == 0) {
bitSet(PORTD, 4);
} else {
bitSet(PORTD, 5);
}
channel[numGate].gateUp = true;
channel[numGate].startTime = dateGateUp;
}
void gateDown(int numGate) {
if (numGate == 0) {
bitClear(PORTD, 4);
} else {
bitClear(PORTD, 5);
}
channel[numGate].gateUp = false;
}
/*
* 1st pot and switches reading.
* Done once, so no need of direct port access
*/
void initPot() {
potCounter = 0;
potDate = millis();
channel[0].potValue = analogRead(A0);
channel[0].jackValue = analogRead(A1);
channel[1].potValue = analogRead(A2);
channel[1].jackValue = analogRead(A3);
channel[0].range = digitalRead(channel[0].pinRange);
channel[1].range = digitalRead(channel[1].pinRange);
updateDuration(0);
updateDuration(1);
}
void updateDuration(int channelNumber) {
int duration;
duration = channel[channelNumber].potValue + (511 - channel[channelNumber].jackValue) * 2; // -1024 to 2046
if (duration < 1) {
duration = 1; // 1 to 2046 ms
}
if (channel[channelNumber].mode == RND) { // RND : from 0 to 2046
channel[channelNumber].upDuration = duration; // for random & bounce
} else {
// other modes : adapting depends on the range (short/long)
if (channel[channelNumber].range) {
duration = duration << 2; // x4 depending on the range : 4 ms to 8174 ms
}
if (channel[channelNumber].mode == TTG) {
channel[channelNumber].upDuration = duration; // gates : 0 to 2 s or 0 to 8 s
} else {
channel[channelNumber].upDuration = duration >> 4; // trigger : 0 to 128 ms or 0 to 512 ms
}
}
}
void updateLED() {
for (int i = 0; i < 2; i++) {
if (channel[i].mode == RND) {
digitalWrite(channel[i].pinLED_mode, stateLed);
} else {
digitalWrite(channel[i].pinLED_mode, channel[i].mode);
}
digitalWrite(channel[i].pinLED_subMode, channel[i].subMode);
}
}