MIDI контроллер на Arduino


В прошлом году я случайно увидел на ютубе видео, где Richie Hawtin показывает свой домашний сетап, крутит ручки у пульта Allen&Heath XONE и управляет тем самым популярной диджейской программой NI Traktor. Меня очень впечатлило это визуально и технически. До этого я не имел ни малейшего понятия о MIDI протоколе и контроллерах, его использующих.


В то время в ящике стола давно валялась плата Arduino и я все хотел пограться с ней, светодиодом я уже помигал, на LCD экран Hello world! вывел, а какого-нибудь применения в голову не приходило. И тут это видео. В общем я решил сделать свой миди-контроллер. Практической цели особо не было, потому как я не диджей, просто хотелось сделать какое-нибудь устройство с нуля до готового продукта.

Самым трудным оказалось найти фейдеры. Купить их в городе невозможно, в российских интернет-магазинах какое то гуано, заказывать за рубежом не хотелось из за Почты России с ее молниеносной доставкой. Я уже, в общем то, хотел сделать все вообще без единого движкового резистора, когда коллега подкинул мне пару старых японских резисторов и я все же поставил один как кроссфейдер. Вообще, я почти не тратил деньги на этот проект и большинство деталей обрели вторую жизнь в этом устройстве. Корпус я пару месяцев до этого извлек из помойки на работе, в нем был собран какой то контроллер разряда аккумулятора (вероятно электропогрузчика, вероятно японского потому что там была дюймовая резьба, которую пришлось перенарезать). Мне понравилось что он литой и основательный. По работе мне часто приходится иметь дело с промышленной электроникой, что конечно наложило свой отпечаток и я постарался сделать устройство максимально технологичным в сборке. Я ненавижу шлейфы проводов вырывающиеся из устройства когда ты откручиваешь его крышку,поэтому я решил сделать мезониную конструкцию или этакий бутерброд из плат. Это несколько сложнее чем просто насверлить дырок в корпусе,вставить в них переменных резисторов и соединить все проводами, но зато у меня в корпусе нет ни одного провода и все разбирается-собирается как АК-47.

Первая плата в «бутерброде» это стандартный макетный «шилд» (shield) арудуино, на котором я по быстрому распаял аналоговый мультиплексор 4051, который занимается тем что переключает сигнал с каждого потенциометра на один из аналоговых входов ардуино. (всего их 6, а мне надо было минимум 8, поэтому пришлось мультиплексировать).

Помимо мультиплексора на плате два светодиода, один из которых индицирует питание через USB, а другой через ключ на транзисторе висит на ноге Tx atmega и мигает при передаче MIDI сообщения.

Вторая плата несет на себе все внешние органы управления ( потенциометры и кнопки) и является фальш панелью. Плата разведена в Layout Sprint и напечатана по кустарной ЛУТ технологии.



При сборке платы последовательно вставляются друг в друга, последняя закрывает корпус, через 4 фторопластовые шайбы накладывается лиецевая панель из матированного оргстекла и весь «бутерброд» стягивается 4 винтами.

Устройство в сборе выглядит так:

Вероятно это самый маленький диджейский миди контроллер).

Что до софтовой части, то примеров полно на форумах по программированию ардуино и большую часть кода написал умный человек, прекрасно комментируя каждую строчку кода. Вот здесь описание этого проекта.
Я легко переписал его под свои нужды не имея опыта программирования на C, добавив обработку мультиплексора.

#include <TimerOne.h>

// Basic MIDI Controller code for reading all of the Arduino's digital and analogue inputs
// and sending them as MIDI messages to the host PC.
//
// Author: Michael Balzer
// Author#2: 2nz
// Revision History:
// Date        |  Change
// ---------------------------------------------------
// 2011-02-22  |  Initial Release
// 2011-03-30  |  Multiplexing 8 Analogue to pin 0
//
// This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// See http://creativecommons.org/licenses/by-nc-sa/3.0/ for license details.

// Uncomment this line to send debug messages to the serial monitor
//#define DEBUG

// MIDI mapping taken from http://www.nortonmusic.com/midi_cc.html
#define MIDI_CC_MODULATION 0x01
#define MIDI_CC_BREATH 0x02
#define MIDI_CC_VOLUME 0x07
#define MIDI_CC_BALANCE 0x08
#define MIDI_CC_PAN 0x0A
#define MIDI_CC_EXPRESSION 0x0B
#define MIDI_CC_EFFECT1 0x0C
#define MIDI_CC_EFFECT2 0x0D

#define MIDI_CC_GENERAL1 0x0E
#define MIDI_CC_GENERAL2 0x0F
#define MIDI_CC_GENERAL3 0x10
#define MIDI_CC_GENERAL4 0x11
#define MIDI_CC_GENERAL5 0x12
#define MIDI_CC_GENERAL6 0x13
#define MIDI_CC_GENERAL7 0x14
#define MIDI_CC_GENERAL8 0x15
#define MIDI_CC_GENERAL9 0x16
#define MIDI_CC_GENERAL10 0x17
#define MIDI_CC_GENERAL11 0x18
#define MIDI_CC_GENERAL12 0x19
#define MIDI_CC_GENERAL13 0x1A
#define MIDI_CC_GENERAL14 0x1B
#define MIDI_CC_GENERAL15 0x1C
#define MIDI_CC_GENERAL16 0x1D
#define MIDI_CC_GENERAL17 0x1E
#define MIDI_CC_GENERAL18 0x1F

#define MIDI_CC_GENERAL1_FINE 0x2E
#define MIDI_CC_GENERAL2_FINE 0x2F
#define MIDI_CC_GENERAL3_FINE 0x30
#define MIDI_CC_GENERAL4_FINE 0x31
#define MIDI_CC_GENERAL5_FINE 0x32
#define MIDI_CC_GENERAL6_FINE 0x33
#define MIDI_CC_GENERAL7_FINE 0x34
#define MIDI_CC_GENERAL8_FINE 0x35
#define MIDI_CC_GENERAL9_FINE 0x36
#define MIDI_CC_GENERAL10_FINE 0x37
#define MIDI_CC_GENERAL11_FINE 0x38
#define MIDI_CC_GENERAL12_FINE 0x39
#define MIDI_CC_GENERAL13_FINE 0x3A
#define MIDI_CC_GENERAL14_FINE 0x3B
#define MIDI_CC_GENERAL15_FINE 0x3C
#define MIDI_CC_GENERAL16_FINE 0x3D
#define MIDI_CC_GENERAL17_FINE 0x3E
#define MIDI_CC_GENERAL18_FINE 0x3F

#define MIDI_CC_SUSTAIN 0x40
#define MIDI_CC_REVERB 0x5B
#define MIDI_CC_CHORUS 0x5D
#define MIDI_CC_CONTROL_OFF 0x79
#define MIDI_CC_NOTES_OFF 0x78



// Comment this line out to disable button debounce logic.
// See http://arduino.cc/en/Tutorial/Debounce what debouncing is used for.
#define DEBOUNCE
// Debounce time length in milliseconds
#define DEBOUNCE_LENGTH 5

// Comment this line out to disable analogue filtering
#define ANALOGUE_FILTER
// A knob or slider movement must initially exceed this value to be recognised as an input. Note that it is
// for a 7-bit MIDI value.
#define FILTER_AMOUNT 5
// Timeout is in microseconds
#define ANALOGUE_INPUT_CHANGE_TIMEOUT 1000000

// Number of digital inputs. Can be anywhere from 0 to 18.
#define NUM_DI 9
// Number of analogue inputs. Can be anywhere from 0 to 6.
//Commented out because of multeplexing to analogue pin0.
//#define NUM_AI 6

// Array containing a mapping of digital pins to channel index. This array size must match NUM_DI above.
byte digitalInputMapping[NUM_DI] = { 5, 6, 7, 8, 9, 10, 11, 12, 13 };
// Array containing a mapping of analogue pins to channel index. This array size must match NUM_AI above.
//Line commented out because of the use multeplexing to the analogue pin0
//byte analogueInputMapping[NUM_AI] = { A0, A1, A2, A3, A4, A5 };

// Contains the current state of the digital inputs.
byte digitalInputs[NUM_DI];
// Contains the current value of the analogue inputs.
byte analogueInputs[8];

// Variable to hold temporary digital reads, used for debounce logic.
byte tempDigitalInput;
// Variable to hold temporary analogue values, used for analogue filtering logic.
byte tempAnalogueInput;

// Preallocate the for loop index so we don't keep reallocating it for every program iteration.
int i = 0;
// Variable to hold difference between current and new analogue input values.
int analogueDiff = 0;
// This is used as a flag to indicate that an analogue input is changing.
boolean analogueInputChanging;

int r0 = 0;      //value of select pin at the 4051 (s0)
int r1 = 0;      //value of select pin at the 4051 (s1)
int r2 = 0;      //value of select pin at the 4051 (s2)

void setup()
{
  // Enable serial I/O at 57600 kbps. This is faster than the standard MIDI rate of 31250 kbps.
  // The PC application which we connect to will automatically take the higher sample rate and send MIDI
  // messages out at the correct rate. We only send things faster in case there is any latency.
  Serial.begin(31250);

  pinMode(2, OUTPUT);    // s0 of 4051
  pinMode(3, OUTPUT);    // s1 of 4051
  pinMode(4, OUTPUT);    // s2 of 4051

  // Initialise each digital input channel.
  for (i = 0; i < NUM_DI; i++)
  {
    // Set the pin direction to input.
    pinMode(digitalInputMapping[i], INPUT);
    // Don't enable pullup resistor on pin 13, as the LED and resistor will always pull it low, meaning the input won't work.
    // Instead an external pulldown resistor must be used on pin 13.
    // NOTE: This will cause all of the high/low logic for pin 13 to be inverted.
    if (digitalInputMapping[i] != 13)
    {
      // Enable the pull-up resistor. This call must come after the above pinMode call.
      digitalWrite(digitalInputMapping[i], HIGH);
    }

    // Initialise the digital state with a read to the input pin.
    digitalInputs[i] = digitalRead(digitalInputMapping[i]);
  }

  // Initialise each analogue input channel.
  // Set the pin 0 direction to input.
    pinMode(0, INPUT);
  for (i = 0; i <=7; i++)
  {
    // multiplexor control
    r0 = i & 0x01;
    r1 = (i>>1) & 0x01;
    r2 = (i>>2) & 0x01;

    digitalWrite(2, r0);
    digitalWrite(3, r1);
    digitalWrite(4, r2);
    // Initialise the analogue value with a read to the input pin.
    analogueInputs[i] = analogRead(0)/8;
  }

  // Assume no analogue inputs are active
  analogueInputChanging = false;

  // This timer runs every 1 second
  Timer1.initialize(ANALOGUE_INPUT_CHANGE_TIMEOUT);
  // When the timer expires, call this function
  Timer1.attachInterrupt(analogueInputStopped);
  // Start the timer
  Timer1.start();

}



void loop()
{
  for (i = 0; i < NUM_DI; i++)
  {
    // Read the current state of the digital input and store it temporarily.
    tempDigitalInput = digitalRead(digitalInputMapping[i]);

    // Check if the last state is different to the current state.
    if (digitalInputs[i] != tempDigitalInput)
    {
      #ifdef DEBOUNCE
      // Wait for a short period of time, and then take a second reading from the input pin.
      delay(DEBOUNCE_LENGTH);
      // If the second reading is the same as the initial reading, assume it must be true.
      if (tempDigitalInput == digitalRead(digitalInputMapping[i]))
      {
      #endif
        // Record the new digital input state.
        digitalInputs[i] = tempDigitalInput;

        // Moved from HIGH to LOW (button pressed)
        if (digitalInputs[i] == 0)
        {
          // All the digital inputs use pullup resistors, except pin 13 so the logic is inverted
          if (digitalInputMapping[i] != 13)
          {
            noteOn(0, 0x00 + i, 0x7F); // Channel 1, middle C, maximum velocity
          }
          else
          {
            noteOff(0, 0x00 + i); // Channel 1, middle C
          }
        }
        // Moved from LOW to HIGH (button released)
        else
        {
          // All the digital inputs use pullup resistors, except pin 13 so the logic is inverted
          if (digitalInputMapping[i] != 13)
          {
            noteOff(0, 0x00 + i); // Channel 1, middle C
          }
          else
          {
            noteOn(0, 0x00 + i, 0x7F); // Channel 1, middle C, maximum velocity
          }
        }
      #ifdef DEBOUNCE
      }
      #endif
    }
  }

  /*
   * Analogue input logic:
   * The Arduino uses a 10-bit (0-1023) analogue to digital converter (ADC) on each of its analogue inputs.
   * The ADC isn't very high resolution, so if a pot is in a position such that the output voltage is 'between'
   * what it can detect (say 2.505V or about 512.5 on a scale of 0-1023) then the value read will constantly
   * fluctuate between two integers (in this case 512 and 513).
   *
   * If we're simply looking for a change in the analogue input value like in the digital case above, then
   * there will be cases where the value is always changing, even though the physical input isn't being moved.
   * This will in turn send out a constant stream of MIDI messages to the connected software which may be problematic.
   *
   * To combat this, we require that the analogue input value must change by a certain threshold amount before
   * we register that it is actually changing. This is good in avoiding a constantly fluctuating value, but has
   * the negative effect of a reduced input resolution. For example if the threshold amount was 2 and we slowly moved
   * a slider through it's full range, we would only detect every second value as a change, in effect reducing the
   * already small 7-bit MIDI value to a 6-bit MIDI value.
   *
   * To get around this problem but still use the threshold logic, a timer is used. Initially the analogue input
   * must exceed the threshold to be detected as an input. Once this occurs, we then read every value coming from the
   * analogue input (not just those exceeding a threshold) giving us full 7-bit resolution. At the same time the
   * timer is started. This timer is used to keep track of whether an input hasn't been moved for a certain time
   * period. If it has been moved, the timer is restarted. If no movement occurs the timer is just left to run. When
   * the timer expires the analogue input is assumed to be no longer moving. Subsequent movements must exceed the
   * threshold amount.
   */
  for (i = 0; i <=7; i++)
  {
    // multiplexor control
    r0 = i & 0x01;
    r1 = (i>>1) & 0x01;
    r2 = (i>>2) & 0x01;

    digitalWrite(2, r0);
    digitalWrite(3, r1);
    digitalWrite(4, r2);

    // Read the analogue input pin 0, dividing it by 8 so the 10-bit ADC value (0-1023) is converted to a 7-bit MIDI value (0-127).
    tempAnalogueInput = analogRead(0) / 8;

    #ifdef ANALOGUE_FILTER
    // Take the absolute value of the difference between the curent and new values
    analogueDiff = abs(tempAnalogueInput - analogueInputs[i]);
    // Only continue if the threshold was exceeded, or the input was already changing
    if ((analogueDiff > 0 && analogueInputChanging == true) || analogueDiff >= FILTER_AMOUNT)
    {
    #else
    if (analogueInputs[i] != tempAnalogueInput)
    {
    #endif
      // If the the analogue input wasn't changing, we need to start the timer again
      if (analogueInputChanging == false)
      {
        Timer1.start();
      }
      // The analogue input was moving, so restart the timer. Only restart it if we're sure the input isn't 'between' a value
      // ie. It's moved more than FILTER_AMOUNT
      else if (analogueDiff >= FILTER_AMOUNT)
      {
        Timer1.restart();
      }

      // The analogue input is moving
      analogueInputChanging = true;

      // Record the new analogue value
      analogueInputs[i] = tempAnalogueInput;

      // Send the analogue value out on the general MIDI CC (see definitions at beginning of this file)
      controlChange(0, MIDI_CC_GENERAL1 + i, analogueInputs[i]);
    }
  }
}

// Send a MIDI note on message
void noteOn(int channel, int pitch, int velocity)
{
  // 0x90 is the first of 16 note on channels
  channel += 0x90;

  // Ensure we're between channels 1 and 16 for a note on message
  if (channel >= 0x90 && channel <= 0x9F)
  {
    #ifdef DEBUG
      Serial.print("Button pressed: ");
      Serial.println(pitch);
    #else
      Serial.print(channel, BYTE);
      Serial.print(pitch, BYTE);
      Serial.print(velocity, BYTE);
    #endif
  }
}

// Send a MIDI note off message
void noteOff(int channel, int pitch)
{
  // 0x80 is the first of 16 note off channels
  channel += 0x80;

  // Ensure we're between channels 1 and 16 for a note off message
  if (channel >= 0x80 && channel <= 0x8F)
  {
    #ifdef DEBUG
      Serial.print("Button released: ");
      Serial.println(pitch);
    #else
      Serial.print(channel, BYTE);
      Serial.print(pitch, BYTE);
      Serial.print(0x00, BYTE);
    #endif
  }
}

// Send a MIDI control change message
void controlChange(int channel, int control, int value)
{
  // 0xB0 is the first of 16 control change channels
  channel += 0xB0;

  // Ensure we're between channels 1 and 16 for a CC message
  if (channel >= 0xB0 && channel <= 0xBF)
  {
    #ifdef DEBUG
      Serial.print(control - MIDI_CC_GENERAL1);
      Serial.print(": ");
      Serial.println(value);
    #else
      Serial.print(channel, BYTE);
      Serial.print(control, BYTE);
      Serial.print(value, BYTE);
    #endif
  }
}

// The timer has expired
void analogueInputStopped()
{
  // Stop the timer so it doesn't repeatedly call this function.
  Timer1.stop();
  // The analogue input is no longer moving
  analogueInputChanging = false;
}

Если опустить детали, то работает это примерно так: При повороте ручки потенциометра меняется напряжение на его среднем выводе ( от 0 до 5 В, что соответствует его крайним положениям), напряжение оцифровывается АЦП и мы получаем байт который преобразуем в формат MIDI сообщения и шлем в последовательный порт, который есть у микроконтроллера для связи с другими цифровыми устройствами. На плате ардуино распаян USB-UART чип FT232 который поднимает виртуальный COM порт на компе. Дальше драйвер древнего синта Rоland который как нельзя кстати создан для работы через COM порт.

И вуаля. Единственная загвоздка это то, что стандартная скорость обмена в MIDI протоколе не стандартна для COM порта, но это быстро пофиксили обитатели форума ардуино, хакнув драйвер FT232.
Необходимо отредактировать файл FTDIPORT.INF

; FTDIPORT.INF
; Copyright (c) 2000-2006 FTDI Ltd.
;
; USB serial port driver installation for Windows 2000 and XP.
;

[Version]
Signature="$Windows NT$"
DriverPackageType=PlugAndPlay
DriverPackageDisplayName=%DESC%
Class=Ports
ClassGUID={4d36e978-e325-11ce-bfc1-08002be10318}
Provider=%FTDI%
CatalogFile=ftdiport.cat
DriverVer=05/19/2006,2.00.00

[SourceDisksNames]
1=%DriversDisk%,,,

[SourceDisksFiles]
ftser2k.sys=1
ftserui2.dll=1
FTLang.Dll = 1
ftcserco.dll = 1

[DestinationDirs]
FtdiPort.NT.Copy=10,system32\drivers
FtdiPort.NT.CopyUI=10,system32
FtdiPort2232.NT.CopyCoInst=10,system32

[ControlFlags]
ExcludeFromSelect=*

[Manufacturer]
%FTDI%=FtdiHw

[FtdiHw]
%VID_0403&PID_6001.DeviceDesc%=FtdiPort232,FTDIBUS\COMPORT&VID_0403&PID_6001
%VID_0403&PID_6010.DeviceDesc%=FtdiPort2232,FTDIBUS\COMPORT&VID_0403&PID_6010

[FtdiPort.NT.AddService]
DisplayName    = %SvcDesc%
ServiceType    = 1                  ; SERVICE_KERNEL_DRIVER
StartType      = 3                  ; SERVICE_DEMAND_START
ErrorControl   = 1                  ; SERVICE_ERROR_NORMAL
ServiceBinary  = %10%\system32\drivers\ftser2k.sys
LoadOrderGroup = Base

; -------------- Serenum Driver install section
[SerEnum_AddService]
DisplayName    = %SerEnum.SvcDesc%
ServiceType    = 1               ; SERVICE_KERNEL_DRIVER
StartType      = 3               ; SERVICE_DEMAND_START
ErrorControl   = 1               ; SERVICE_ERROR_NORMAL
ServiceBinary  = %12%\serenum.sys
LoadOrderGroup = PNP Filter

[FtdiPort.NT.AddReg]
HKR,,EnumPropPages32,,"ftserui2.dll,SerialPortPropPageProvider"

[FtdiPort.NT.Copy]
ftser2k.sys
;serenum.sys

[FtdiPort.NT.CopyUI]
ftserui2.dll
FTLang.dll

[FtdiPort232.NT]
CopyFiles=FtdiPort.NT.Copy,FtdiPort.NT.CopyUI
AddReg=FtdiPort.NT.AddReg

[FtdiPort232.NT.HW]
AddReg=FtdiPort232.NT.HW.AddReg

[FtdiPort232.NT.Services]
AddService = FTSER2K, 0x00000002, FtdiPort.NT.AddService
AddService = Serenum,,SerEnum_AddService
DelService = FTSERIAL

[FtdiPort232.NT.HW.AddReg]
HKR,,"UpperFilters",0x00010000,"serenum"
;HKR,,"ConfigData",1,01,00,3F,3F,10,27,88,13,C4,09,E2,04,71,02,38,41,9c,80,4E,C0,34,00,1A,00,0D,00,06,40,03,80,00,00,d0,80
;HKR,,"ConfigData",1,11,00,3F,3F,10,27,00,00,88,13,00,00,C4,09,00,00,E2,04,00,00,71,02,00,00,38,41,00,00,9C,80,00,00,4E,C0,00,00,34,00,00,00,1A,00,00,00,0D,00,00,00,06,40,00,00,03,80,00,00,00,00,00,00,D0,80,00,00
HKR,,"ConfigData",1,11,00,3F,3F,10,27,00,00,88,13,00,00,C4,09,00,00,E2,04,00,00,71,02,00,00,38,41,00,00,9C,80,00,00,60,00,00,00,34,00,00,00,1A,00,00,00,0D,00,00,00,06,40,00,00,03,80,00,00,00,00,00,00,D0,80,00,00
HKR,,"MinReadTimeout",0x00010001,0
HKR,,"MinWriteTimeout",0x00010001,0
HKR,,"LatencyTimer",0x00010001,16

; -------
; FT2232C
; -------

[FtdiPort2232.NT]
CopyFiles=FtdiPort.NT.Copy,FtdiPort.NT.CopyUI
AddReg=FtdiPort.NT.AddReg

[FtdiPort2232.NT.HW]
AddReg=FtdiPort232.NT.HW.AddReg

[FtdiPort2232.NT.CoInstallers]
AddReg=FtdiPort2232.NT.CoInstallers.AddReg
CopyFiles=FtdiPort2232.NT.CopyCoInst

[FtdiPort2232.NT.Services]
AddService = FTSER2K, 0x00000002, FtdiPort.NT.AddService
AddService = Serenum,,SerEnum_AddService
DelService = FTSERIAL

[FtdiPort2232.NT.CoInstallers.AddReg]
HKR,,CoInstallers32,0x00010000,"ftcserco.Dll,FTCSERCoInstaller"

[FtdiPort2232.NT.CopyCoInst]
ftcserco.dll

;---------------------------------------------------------------;

[Strings]
FTDI="FTDI"
DESC="CDM Driver Package"
DriversDisk="FTDI USB Drivers Disk"
PortsClassName = "Ports (COM & LPT)"
VID_0403&PID_6001.DeviceDesc="USB Serial Port"
VID_0403&PID_6010.DeviceDesc="USB Serial Port"
SvcDesc="USB Serial Port Driver"
SerEnum.SvcDesc="Serenum Filter Driver"


Маководы могут воспользоваться вот этим приложением
Ну и наконец в вашем любимой музыкальной программе любые элементы интерфейса раскидываются на кнопки и крутилки.

Вот небольшое видео в общих чертах демонстрирующее работу контроллера:


0 комментариев на «“MIDI контроллер на Arduino”»

  1. Необходимо отредактировать файл FTDIPORT.INF

    где его отредактировать? в системной папке? «C:\Windows\System32\DriverStore\FileRepository\ftdiport.inf_amd64_neutral_9b2b9fd5d576957d\FTDIPORT.INF», он пишет что прав нету на редактирование а также на замену. Как сделать?? win7 64bit

  2. а я например хочу более широкий функционал, пару энкодеров добавить, поболее переменников, ещё есть мысль что бы принимала плата миди и зажигала нужные светодиоды, кто подскажет каким образом?

    • да и ещё, в этой сборке команды сразу посылаются? без промежуточных программ?

    • Я использовал роландовский COM драйвер и в тексте это указано. Где и как я его нашел уже и не вспомнить.

    • Без проблем. Прикручивай что хочешь, хоть энкодеры, хоть любые датчики. Мультиплексируй переменники. По реализации этот проект очень старый. Возможно эта библиотека может облегчить написание своей версии:

  3. А под большее кол-во потенциометров и кнопок ее можно допилить. С аппаратной точки зрения проблем нет: ставим 4 мелкосхемки CD4066 и 1 74HC165, после чего аппаратная часть обслужит 32 потенциометра и галетник на 6 позиций. А вот с программной точки как??? Что, где, как в программе надо поправить для работы 4 мультиплексоров и 2 регистров. Дуинку изучать начал недавно, т.ч прошу тапочками не кидаться.

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение