Программирование ARDUINO

КВАНТОЛАБ г. Полярный



3D роботизированная рука на Arduino с записью и воспроизведением действий

3D роботизированная рука на Arduino с записью и воспроизведением действий

Роботизированные руки с каждым годом находят все большее применение в жизни современного общества, особенно в тех приложениях, в которых требуется скорость, точность и безопасность действий. Их можно не только увидеть в современных кинофильмах, например, в серии фильмов "Железный человек", но их еще в большом количестве и ассортименте производят такие компании как Fanuc, Kuka, Denso, ABB, Yaskawa и т.д. Эти роботизированные руки используются на производственных линиях по сборке автомобилей, горнодобывающей промышленности, химических заводах и т.п.

Внешний вид 3D роботизированной руки на Arduino

В этой статье мы рассмотрим создание роботизированной руки (Robotic Arm) с помощью платы Arduino и сервомоторов MG995. Эта роботизированная рука будет иметь 4 степени свободы (без учета захвата) и управляться с помощью потенциометров. Кроме этого, мы также сможем записывать и в дальнейшем воспроизводить все движения руки – именно так во многих случаях и программируются роботизированные руки для работы на реальных производствах.

Необходимые компоненты

  1. Плата Arduino Nano (купить на AliExpress).
  2. Сервомотор MG-995 (5 шт.) (купить на AliExpress).
  3. Потенциометр (5 шт.) (купить на AliExpress).
  4. Перфорированная плата.
  5. Скелет роботизированной руки и крепления.

Примечание: скелет данной роботизированной руки полностью напечатан на 3D принтере. Если у вас есть доступ к 3D принтеру, то вы можете самостоятельно напечатать все части этой руки на нем по приведенным ниже файлам. Если у вас нет доступа к 3D принтеру, то вы можете изготовить скелет этой руки из акрилового волокна или дерева. В простейшем случае вы можете изготовить скелет этой руки из обычных листов картона, как сделано в простейшей роботизированной руке на основе платы Arduino.

3D печать и сборка роботизированной руки

Вначале мы пытались разработать дизайн этой руки самостоятельно, но потом обнаружили что на сервисе Thingiverse есть достаточно много потрясающих дизайнов подобных роботизированных рук и решили "не изобретать заново велосипед". Мы обнаружили, что роботизированная рука Robotic Arm V2.0 by Ashing будет прекрасно работать с сервомоторами MG995, поэтому она отлично подходит для нашего проекта.

Поэтому перейдите по приведенной ссылке на Thingiverse и скачайте там файлы модели этой руки. Всего рука содержит 14 компонентов для 3D печати, STL файлы для всех из них можно скачать по приведенной ссылке. Мы использовали программное обеспечение Cura 3.2.1 от Ultimaker для обработки STL файлов и 3D принтер TEVO tarantula для печати компонентов руки.

Настройки печати для роботизированной руки

Дизайн всех компонентов этой руки достаточно прост, поэтому его можно напечатать практически на любом 3D принтере. У нас на печать всех компонентов руки ушло примерно 4,5 часа. Инструкция по сборке руки приведена на этой странице, поэтому в нашей статье не будем останавливаться на ней подробно.

Напечатанные компоненты роботизированной руки

Единственный момент, на который нам бы хотелось обратить внимание – возможно вам придется вручную подровнять (подшлифовать) края некоторых компонентов чтобы "втиснуть" туда сервомотор - обычно крепления для сервомоторов проектируются с таким расчетом, чтобы запихивать в них сервомоторы приходилось с некоторым усилием, так они будут лучше держаться. Вам понадобится 20 болтов диаметром 3 мм для сборки компонентов этой роботизированной руки.

Перед тем как окончательно закрутить болты удостоверьтесь в том, что сервомоторам ничего не мешает вращаться в необходимых направлениях. Провода к трем сервомоторам вам придется тянуть снаружи конструкции руки, при необходимости удлинить эти провода можно использовать соединители папа-мама. Удостоверьтесь в том, что при работе руки эти провода не будут перекручиваться или каким либо другим образом мешать работе руки. После сборки у нас получилась рука следующего вида:

Собранная роботизированная рука

 

Работа схемы

Схема роботизированной руки на Arduino представлена на следующем рисунке.

Схема роботизированной руки на Arduino

Поскольку сервомоторы MG995 работают от питающего напряжения 5V, то их можно запитать от соответствующего разъема платы Arduino. Управляющие контакты сервомоторов подключены к цифровым контактам платы Arduino, на которых возможно формирование сигналов ШИМ (широтно-импульсной модуляции). Потенциометры подключены к аналоговым контактам платы Arduino для управления сервомоторами.

В любой момент времени у нас будет двигаться только один сервомотор, поэтому потребляемый ток не превысит 150 мА, в связи с чем регулятор питания платы Arduino без проблем выдержит такую нагрузку. Сама плата Arduino в нашем проекте запитывалась через USB кабель от компьютера.

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

Собранная на перфорированной плате схема управления роботизированной рукой

Если вы раньше не сталкивались с сервомоторами рекомендуем прочитать статью про подключение сервомотора к плате Arduino.

Объяснение программы для Arduino

В программе мы должны предусмотреть возможность записи движений пользователя по вращению ручек потенциометров и их воспроизведения когда это потребуется. Поэтому в программе нам необходимо два режима – режим записи (Record mode) и режим воспроизведения (Play mode). Пользователь сможет переключаться между этими двумя режимами с помощью монитора последовательной связи (serial monitor). Полный текст программы приведен в конце данной статьи, здесь же мы рассмотрим его наиболее важные фрагменты.

Как обычно, программу начнем с подключения необходимых заголовочных файлов. Мы будем использовать библиотеку Servo.h для управления сервомоторами. У нас 5 сервомоторов в проекте, поэтому каждому из них необходимо дать уникальное имя. Также мы инициализируем переменные, которые затем будем использовать в программе. Мы сделали все переменные глобальными – но вы самостоятельно можете оптимизировать эту часть программы если захотите. Также мы инициализировали массив saved_data, в который мы будем записывать движения руки.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#include //Servo header file

 

//объявим объекты для 5 сервомоторов

Servo Servo_0;

Servo Servo_1;

Servo Servo_2;

Servo Servo_3;

Servo Gripper;

 

//глобальные объявления переменных

int S0_pos, S1_pos, S2_pos, S3_pos, G_pos;

int P_S0_pos, P_S1_pos, P_S2_pos, P_S3_pos, P_G_pos;

int C_S0_pos, C_S1_pos, C_S2_pos, C_S3_pos, C_G_pos;

int POT_0,POT_1,POT_2,POT_3,POT_4;

 

int saved_data[700]; //массив для записи движений руки

 

int array_index=0;

char incoming = 0;

 

int action_pos;

int action_servo;

Внутри функции void setup мы инициализируем последовательную связь со скоростью 9600 бод/с. Также мы укажем плате Arduino к каким ее контактам подключены сервомоторы – в нашем случае это контакты 3,5,6,9 и 10. Также в функции setup поскольку она запускается при подаче питания на плату Arduino мы зададим начальное положение руки с помощью поворотов осей сервомоторов на заданные углы. Также в функции setup мы выведем в окно монитора последовательной связи сообщение с просьбой нажать клавишу R или P чтобы потом мы могли выполнить заданное действие (запись или воспроизведение).

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

void setup() {

Serial.begin(9600); //последовательная связь для целей отладки

 

//контакты, к которым подключены сервомоторы

Servo_0.attach(3);

Servo_1.attach(5);

Servo_2.attach(6);

Servo_3.attach(9);

Gripper.attach(10);

 

//поворачиваем сервомоторы в начальное положение для руки

Servo_0.write(70);

Servo_1.write(100);

Servo_2.write(110);

Servo_3.write(10);

Gripper.write(10);

 

Serial.println("Press 'R' to Record and 'P' to play"); //Instrust the user

}

Также мы запрограммируем функцию Read_POT, которая будет считывать аналоговые значения с выходов всех потенциометров и преобразовывать их в значения для позиций сервомоторов. Как мы знаем, значения на выходе АЦП (аналогово-цифровой преобразователь) платы Arduino могут изменяться в диапазоне 0-1023, а позиция осей сервомоторов может изменяться только в диапазоне 0-180. Поскольку сервомоторы у нас бюджетные и потому не очень точные, то в целях безопасности целесообразно сузить диапазон их используемых углов поворота с 0-180 до 0-170. Поэтому мы будем использовать функцию map чтобы конвертировать диапазон 0-1023 в диапазон 10-170 для всех наших 5 сервомоторов.

1

2

3

4

5

6

7

8

9

void Read_POT() //функция для считывания аналоговых значений с потенциометров и преобразования их в значения для сервомоторов

{

   POT_0 = analogRead(A0); POT_1 = analogRead(A1); POT_2 = analogRead(A2); POT_3 = analogRead(A3); POT_4 = analogRead(A4); //Read the Analog values form all five POT

   S0_pos = map(POT_0,0,1024,10,170); //Map it for 1st Servo (базовый мотор)

   S1_pos = map(POT_1,0,1024,10,170); //Map it for 2nd Servo (мотор для бедра)

   S2_pos = map(POT_2,0,1024,10,170); //Map it for 3rd Servo (мотор для плеча)

   S3_pos = map(POT_3,0,1024,10,170); //Map it for 4th Servo (мотор для шеи)

   G_pos  = map(POT_4,0,1024,10,170);  //Map it for 5th Servo (мотор для захвата)

}

 

Код для режима записи

В режиме записи мы должны записывать все движения руки – то есть позицию и номер каждого сервомотора. Эти данные мы будем записывать в массив saved_data.

 

При работе с сервомоторами существует такая проблема как их дрожание/колебание (jitter) во время их работы. Для решения этой проблемы существует множество способов. Но первоначально вы должны определить из-за чего именно возникает эта проблема – из-за схемы управления сервомотором или из-за неточности значения позиции, которая записывается в сервомотор. В нашем случае мы использовали монитор последовательной связи и обнаружили что значение позиции сервомотора не является постоянным и иногда колеблется/изменяется случайным образом.

Таким образом, мы запрограммировали плату Arduino таким образом, чтобы считывать значения с выходов потенциометров дважды и затем сравнивать их между собой. Значение признается истинным (правильным) только тогда когда оба значения совпадают, иначе значение игнорируется. И этот прием решил проблему колебания/джиттера сервомоторов в нашем случае. Также убедитесь в том, что потенциометры надежно (крепко) подсоединены к аналоговым контактам платы Arduino (в нашем случае они припаяны). Любое не очень надежное соединение может приводить к джиттеру. Переменная P_x_pos используется для сохранения старых значений потенциометров, а новые значения потенциометров считываются и преобразуются в заданный диапазон с помощью ранее описанной функции Read_POT.

1

2

3

4

5

6

7

8

Read_POT(); //считываем значения с потенциометров в 1-й раз

//сохраняем их в переменных чтобы сравнить их потом

   P_S0_pos = S0_pos;

   P_S1_pos = S1_pos;

   P_S2_pos = S2_pos;

   P_S3_pos = S3_pos;

   P_G_pos  = G_pos;

Read_POT(); //считываем значения с потенциометров во 2-й раз

Теперь, мы должны устанавливать позицию сервомотора если значение истинно. После этого мы еще должны сохранить номер сервомотора и его позицию в массив. Можно было бы, конечно, использовать два массива для раздельного хранения номера сервомотора и его позиции, но в целях экономии памяти и уменьшения сложности программы мы решили использовать один массив при помощи добавления разделителя к позиции перед сохранением ее в массиве.

1

2

3

4

5

6

7

8

9

10

11

12

if (P_S0_pos == S0_pos) //если старое и новое значения совпадают

   {

    Servo_0.write(S0_pos); //управляем позицией оси сервомотора

  

    if (C_S0_pos != S0_pos) //если значение с выхода потенциометра изменилось

    {

      saved_data[array_index] = S0_pos + 0; //сохраняем новую позицию в массив. Ноль добавляется для нулевого мотора (для понимания сути операции)

      array_index++; //увеличиваем индекс массива

    }

  

    C_S0_pos = S0_pos; //сохраняем предыдущее значение чтобы потом проверять изменилось ли значение на выходе потенциометра (повернули ли его ручку)

   }

Число разделитель в массиве для нулевого сервомотора (Servo_0)  у нас будет 0, для Servo_1 – 1000 и т.д. для Servo_3 – 3000, а для захвата (Gripper) – 4000. Фрагмент кода, в котором в массив записываются наши данные с добавлением разделителя, выглядит следующим образом:

1

2

3

4

5

saved_data[array_index] = S0_pos + 0; //сохраняем новую позицию в массив. Ноль в качестве разделителя добавляется для нулевого сервомотора (для понимания сути операции)

saved_data[array_index] = S1_pos + 1000; //1000 добавляется для 1-го сервомотора в качестве разделителя

saved_data[array_index] = S2_pos + 2000; //2000 добавляется для 2-го сервомотора в качестве разделителя

saved_data[array_index] = S3_pos + 3000; //3000 добавляется для 3-го сервомотора в качестве разделителя

saved_data[array_index] = G_pos + 4000; //4000 добавляется для 4-го сервомотора (захват) в качестве разделителя

 

Код для режима воспроизведения

Когда у нас все движения сервомоторов уже записаны в массиве saved_data мы можем переключиться в режим воспроизведения этих действий при помощи ввода символа ‘P” в окне монитора последовательной связи. В режиме воспроизведения у нас есть доступ к каждому элементу этого массива и, зная известные нам разделители, мы можем разделить значения этого массива чтобы извлечь из них номер мотора и его позицию.

Мы будем использовать цикл for для навигации по элементам массива. Затем в две переменные с именами action_servo и action_pos мы будем записывать номер сервомотора и его позицию соответственно. Чтобы определить номер сервомотора мы будем делить значение из массива на 1000, а позиция сервомотора будет извлекаться из трех последних цифр этого числа (значения из массива).

К примеру, если в массиве сохранено значение 3125, то это будет означать, что 3-й сервомотор необходимо повернуть в позицию 125.

1

2

3

4

  for (int Play_action=0; Play_action

  {

    action_servo = saved_data[Play_action] / 1000; //первый символ элемента массива будет обозначать номер сервомотора

    action_pos = saved_data[Play_action] % 1000; // три последних символа элемента массива будут обозначать позицию сервомотора

После этого все, что остается сделать, это повернуть нужный сервомотор на заданную позицию. Для выполнения этих операций мы использовали оператор выбора switch case.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

    switch(action_servo){ //проверяем каким сервомотором необходимо управлять

      case 0: //Если это 0-й мотор

        Servo_0.write(action_pos);

      break;

 

      case 1:// Если это 1-й мотор

        Servo_1.write(action_pos);

      break;

 

      case 2:// Если это 2-й мотор

        Servo_2.write(action_pos);

      break;

 

      case 3:// Если это 3-й мотор

        Servo_3.write(action_pos);

      break;

 

      case 4:// Если это 4-й мотор

        Gripper.write(action_pos);

      break;

 

Основная функция loop

Внутри основной функции программы loop нам необходимо всего лишь проверять что пользователь ввел в окне монитора последовательной связи и, соответственно, запускать на выполнение режим записи или режим воспроизведения. Если пользователь нажал ‘R’ – активируется режим записи, а если ‘P’ – режим воспроизведения.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

void loop() {

 

if (Serial.available() > 1) //если что то принято из последовательного монитора

{

incoming = Serial.read();

if (incoming == 'R')

Serial.println("Robotic Arm Recording Started......");

if (incoming == 'P')

Serial.println("Playing Recorded sequence");

}

 

if (incoming == 'R') //если пользователь выбрал режим записи

Record();

 

if (incoming == 'P') //если пользователь выбрал режим воспроизведения

Play();

}

 

Тестирование работы роботизированной руки

Сделайте все соединения, которые показаны на выше приведенной схеме, и загрузите программу в плату Arduino. Подайте питание на плату Arduino Nano через порт USB с вашего компьютера и откройте окно монитора последовательной связи – в нем вы увидите приветственное сообщение.

Приветственное сообщение в окне монитора последовательной связи

 

 

Теперь введите R в окне монитора последовательной связи и нажмите ввод. Внизу монитора последовательной связи должна быть установлена опция Newline. Пример работы программы в этом режиме показан на следующем рисунке:

Пример работы проекта в режиме записи

Информация, показанная на этом рисунке, может быть использована для отладки. Цифры, начинающиеся с 69, это текущая позиция сервомоторов с 0-го до 5-го. Значения индекса – это размер массива. Помните, что максимальный размер массива ограничен 700 числами, поэтому старайтесь не превышать этот размер. После того как вы завершите запись нажмите P и ввод в окне монитора последовательной связи и программа переключится в режим воспроизведения и на экране тогда появится примерно следующая картина:

Пример работы проекта в режиме воспроизведения

Во время режима воспроизведения роботизированная рука будет повторять те же самые движения, которые она совершала в режиме записи. Эти движения она будет выполнять снова и снова до тех пор пока вы не прервете ее работу из окна монитора последовательной связи.

Исходный код программы (скетча)

Код программы достаточно подробно объяснен выше в статье, поэтому, я надеюсь, вы без особо труда разберетесь в нем и при необходимости измените его по своему желанию. Перевод комментариев к коду программы также представлен в предыдущем разделе статьи, здесь второй раз решил не переводить.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

/*

   Robotic ARM with Record and Play option using Arduino

   Code by: B. Aswinth Raj

   Website: http://www.circuitdigest.com/

   Dated: 05-08-2018

*/

#include //Servo header file

//Declare object for 5 Servo Motors  

Servo Servo_0;

Servo Servo_1;

Servo Servo_2;

Servo Servo_3;

Servo Gripper;

//Global Variable Declaration

int S0_pos, S1_pos, S2_pos, S3_pos, G_pos;

int P_S0_pos, P_S1_pos, P_S2_pos, P_S3_pos, P_G_pos;

int C_S0_pos, C_S1_pos, C_S2_pos, C_S3_pos, C_G_pos;

int POT_0,POT_1,POT_2,POT_3,POT_4;

int saved_data[700]; //Array for saving recorded data

int array_index=0;

char incoming = 0;

int action_pos;

int action_servo;

void setup() {

Serial.begin(9600); //Serial Monitor for Debugging

//Declare the pins to which the Servo Motors are connected to

Servo_0.attach(3);

Servo_1.attach(5);

Servo_2.attach(6);

Servo_3.attach(9);

Gripper.attach(10);

//Write the servo motors to initial position

Servo_0.write(70);

Servo_1.write(100);

Servo_2.write(110);

Servo_3.write(10);

Gripper.write(10);

Serial.println("Press 'R' to Record and 'P' to play"); //Instruct the user

}

void Read_POT() //Function to read the Analog value form POT and map it to Servo value

{

   POT_0 = analogRead(A0); POT_1 = analogRead(A1); POT_2 = analogRead(A2); POT_3 = analogRead(A3); POT_4 = analogRead(A4); //Read the Analog values form all five POT

   S0_pos = map(POT_0,0,1024,10,170); //Map it for 1st Servo (Base motor)

   S1_pos = map(POT_1,0,1024,10,170); //Map it for 2nd Servo (Hip motor)

   S2_pos = map(POT_2,0,1024,10,170); //Map it for 3rd Servo (Shoulder motor)

   S3_pos = map(POT_3,0,1024,10,170); //Map it for 4th Servo (Neck motor)

   G_pos  = map(POT_4,0,1024,10,170);  //Map it for 5th Servo (Gripper motor)

}

void Record() //Function to Record the movements of the Robotic Arm

{

Read_POT(); //Read the POT values  for 1st time

//Save it in a variable to compare it later

   P_S0_pos = S0_pos;

   P_S1_pos = S1_pos;

   P_S2_pos = S2_pos;

   P_S3_pos = S3_pos;

   P_G_pos  = G_pos;

  

Read_POT(); //Read the POT value for 2nd time

  

   if (P_S0_pos == S0_pos) //If 1st and 2nd value are same

   {

    Servo_0.write(S0_pos); //Control the servo

    

    if (C_S0_pos != S0_pos) //If the POT has been turned

    {

      saved_data[array_index] = S0_pos + 0; //Save the new position to the array. Zero is added for zeroth motor (for understading purpose)

      array_index++; //Increase the array index

    }

    

    C_S0_pos = S0_pos; //Saved the previous value to check if the POT has been turned

   }

//Similarly repeat for all 5 servo Motors

   if (P_S1_pos == S1_pos)

   {

    Servo_1.write(S1_pos);

    

    if (C_S1_pos != S1_pos)

    {

      saved_data[array_index] = S1_pos + 1000; //1000 is added for 1st servo motor as differentiator

      array_index++;

    }

    

    C_S1_pos = S1_pos;

   }

   if (P_S2_pos == S2_pos)

   {

    Servo_2.write(S2_pos);

    

    if (C_S2_pos != S2_pos)

    {

      saved_data[array_index] = S2_pos + 2000; //2000 is added for 2nd servo motor as differentiator

      array_index++;

    }

    

    C_S2_pos = S2_pos;

   }

   if (P_S3_pos == S3_pos)

   {

    Servo_3.write(S3_pos);

    

    if (C_S3_pos != S3_pos)

    {

      saved_data[array_index] = S3_pos + 3000; //3000 is added for 3rd servo motor as differentiater

      array_index++;

    }

    

    C_S3_pos = S3_pos;  

   }

   if (P_G_pos == G_pos)

   {

    Gripper.write(G_pos);

    

    if (C_G_pos != G_pos)

    {

      saved_data[array_index] = G_pos + 4000; //4000 is added for 4th servo motor as differentiator

      array_index++;

    }

    

    C_G_pos = G_pos;

   }

  

  //Print the value for debugging

  Serial.print(S0_pos);  Serial.print("  "); Serial.print(S1_pos); Serial.print("  "); Serial.print(S2_pos); Serial.print("  "); Serial.print(S3_pos); Serial.print("  "); Serial.println(G_pos);

  Serial.print ("Index = "); Serial.println (array_index);

  delay(100);

}

void Play() //Functon to play the recorded movements on the Robotic ARM

{

  for (int Play_action=0; Play_action

  {

    action_servo = saved_data[Play_action] / 1000; //The fist character of the array element is split for knowing the servo number

    action_pos = saved_data[Play_action] % 1000; //The last three characters of the array element is split to know the servo postion

    switch(action_servo){ //Check which servo motor should be controlled

      case 0: //If zeroth motor

        Servo_0.write(action_pos);

      break;

      case 1://If 1st motor

        Servo_1.write(action_pos);

      break;

      case 2://If 2nd motor

        Servo_2.write(action_pos);

      break;

      case 3://If 3rd motor

        Servo_3.write(action_pos);

      break;

      case 4://If 4th motor

        Gripper.write(action_pos);

      break;

    }

    delay(50);

    

  }

}

void loop() {

if (Serial.available() > 1) //If something is received from serial monitor

{

incoming = Serial.read();

if (incoming == 'R')

Serial.println("Robotic Arm Recording Started......");

if (incoming == 'P')

Serial.println("Playing Recorded sequence");

}

if (incoming == 'R') //If user has selected Record mode

Record();

if (incoming == 'P') //If user has selected Play Mode

Play();

}



Дата: 2023-07-26   Автор: Статья взята с microkontroller.ru   Просмотров: 2429





Контакты

Если у Вас есть вопросы, мы с удовольствием на них ответим.

Адрес:

Мурманская область, г.Полярный ул. Красный Горн, д.16, 3-й этаж, 34 каб.

Телефон:

+7 9646829261