Сегодня вы вряд ли кого-нибудь удивите мобильным телефоном с камерой, беспроводными гаджетами и другими техническими достижениями. Благодаря платформе Arduino миллионы людей открыли для себя удивительный мир электроники и программирования. Было уже написано множество инструкций о том, как обмениваться данными между мобильным телефоном и Arduino через Bluetooth. В этом примере мы тоже будем обмениваться данными между мобильным телефоном на Android и Arduino UNO через Bluetooth, но мы будем передавать не просто набор символов и цифр, а изображение.

Кто-то скажет, что это невозможно, Arduino слишком медленно обрабатывает большие объемы данных с хорошей скоростью. И он будет абсолютно прав. А что, если немного помочь Arduino – перенести всю «тяжелую» работу на плечи другого устройства? И такое устройство есть.
Это уникальный TFT-щилд для Arduino. В этом уроке мы покажем, как можно подключиться через Bluetooth к Arduino и телефону Android, получить изображение с камеры OV7670 на Arduino UNO и перенести его на телефон Android. Затем, наоборот, перенесем изображение с камеры телефона Android на Arduino UNO и отобразим его на экране специального TFT-экрана.
Помимо Arduino и TFT-щилда основными компонентами данного проекта также являются камера OV7670 и Bluetooth-модуль HC-06 (HC-05).

Все демонстрационные скетчи написаны в среде Arduino IDE, поэтому вначале необходимо установить Arduino IDE – https://www.arduino.cc/en/main/software. Затем необходимо установить библиотеку для TFT-щилда – github.com/YATFT/YATFT. После установки Arduino IDE необходимо запрограммировать плату Arduino UNO. Для простоты рекомендуем прошивать отдельно, без экрана TFT. Код основного файла ArduinoBluetoothCamera.ino следующий:
#include <YATFT.h>
#include <util/yacam.h>
#include <util/yacodec.h>
#include <util/yasrl.h>
#include "ov7670_regs.h"
JPEG_DECODE jpeg_decode;
YATFT tft(0);
INTRFC ifc;
CAM cam;
CODEC codec;
SRL srl;
#define IMG_SizeX 320
#define IMG_SizeY 240
uint8_t mode = 0;
uint8_t last_mode = 0;
uint8_t start_capt = 0;
uint16_t err;
void setup()
{
Serial.begin(115200);
tft.begin();
tft.SetColor(BRIGHTRED);
tft.ClearDevice();
}
void loop()
{
if (Serial.available())
{
uint8_t temp = Serial.read();
switch (temp)
{
case 0x10:
mode = 1;
start_capt = 1;
if (last_mode != mode && last_mode != 2) {
tft.begin();
tft.SetRGB();
cam.CamInit(&OV7670_QVGA[0][0]);
cam.CamVideoPreview(0, 0, 1, true);
codec.JPEGInit();
codec.JPEGSetRegs(IMG_SizeX, IMG_SizeY);
delay(1000);
}
break;
case 0x20:
mode = 2;
start_capt = 2;
if (last_mode != mode && last_mode != 1) {
tft.begin();
tft.SetRGB();
cam.CamInit(&OV7670_QVGA[0][0]);
cam.CamVideoPreview(0, 0, 1, true);
codec.JPEGInit();
codec.JPEGSetRegs(IMG_SizeX, IMG_SizeY);
}
break;
case 0x30:
mode = 3;
start_capt = 3;
if (last_mode != mode && last_mode != 4) {
tft.begin();
tft.SetYUV();
}
break;
case 0x40:
mode = 4;
start_capt = 4;
if (last_mode != mode && last_mode != 3) {
tft.begin();
tft.SetYUV();
}
break;
default:
break;
}
}
if (mode == 1)
{
if (start_capt == 1)
{
start_capt = 0;
last_mode = mode;
mode = 0;
CamCapture();
}
}
else if (mode == 2)
{
while (1)
{
uint8_t temp = Serial.read();
if (start_capt == 2)
{
start_capt = 0;
}
if (temp == 0x21)
{
start_capt = 0;
last_mode = mode;
mode = 0;
break;
}
if (CamCapture()) continue;
}
}
else if (mode == 3)
{
if (start_capt == 3)
{
start_capt = 0;
last_mode = mode;
mode = 0;
Serial.print("!");
srl.JPEGReadFromSerial(&jpeg_decode,
0,
0,
GetMaxX()+1,
GetMaxY()+1,
32000);
}
}
else if (mode == 4)
{
uint8_t temp = Serial.read();
while (1)
{
if (start_capt == 4)
{
start_capt = 0;
}
if (temp == 0x41)
{
start_capt = 0;
last_mode = mode;
mode = 0;
break;
}
Serial.print("!");
srl.JPEGReadFromSerial(&jpeg_decode,
0,
0,
GetMaxX()+1,
GetMaxY()+1,
32000);
}
}
}
uint8_t CamCapture(void)
{
uint8_t temp = 0xff, temp_last = 0;
bool is_header = false;
uint32_t length = 0;
length = codec.JPEGStart();
uint32_t en_jpeg_address = ifc.rdReg32(0x414)<<2;
int k = 0;
if ((length >= 0x5FFFF) | (length == 0))
{
start_capt = 2;
return 1;
}
temp = ifc.GetMem(en_jpeg_address+k);
k++;
length --;
while ( length-- )
{
temp_last = temp;
temp = ifc.GetMem(en_jpeg_address+k);
k++;
if (is_header == true)
{
Serial.write(temp);
}
else if ((temp == 0xD8) & (temp_last == 0xFF))
{
is_header = true;
Serial.write(temp_last);
Serial.write(temp);
}
if ( (temp == 0xD9) && (temp_last == 0xFF) )
break;
}
is_header = false;
return 0;
}
Код заголовочного файла OV7670_regs.h:
#ifndef OV7670_REGS_H
#define OV7670_REGS_H
const uint8_t OV7670_VGA[][2] PROGMEM =
{
{ 1, 0x42},
{ 640/16, 480/16},
{0b01000010, 0b00000100}, // Reset_Enable_N, 7|6|5|4|3|Vsync Invert|Hsync Invert|0
{0x3a, 0x0C}, {0x40, 0xC0}, {0x12, 0x00}, {0x0c, 0x00},
{0x3e, 0x00}, {0x70, 0x3A}, {0x71, 0x35}, {0x72, 0x11},
{0x73, 0xF0}, {0xa2, 0x02}, {0x11, 0x80}, {0x7a, 0x18},
{0x7b, 0x02}, {0x7c, 0x07}, {0x7d, 0x1F}, {0x7e, 0x49},
{0x7f, 0x5A}, {0x80, 0x6A}, {0x81, 0x79}, {0x82, 0x87},
{0x83, 0x94}, {0x84, 0x9F}, {0x85, 0xAF}, {0x86, 0xBB},
{0x87, 0xCF}, {0x88, 0xEE}, {0x89, 0xEE}, {0x13, 0xE0},
{0x00, 0x00}, {0x10, 0x00}, {0x0d, 0x00}, {0x24, 0x98},
{0x25, 0x63}, {0x26, 0xD3}, {0x2a, 0x00}, {0x2b, 0x00},
{0x2d, 0x00}, {0x13, 0xe5}, {0x13, 0xe7}, {0x1e, 0x30},
{0x74, 0x60}, {0x01, 0x80}, {0x02, 0x80}, {0x15, 0x10},
{0x4f, 0x40}, {0x50, 0x34}, {0x51, 0x0C}, {0x52, 0x17},
{0x53, 0x29}, {0x54, 0x40}, {0x57, 0x80}, {0x58, 0x1e},
{0x41, 0x10}, {0x75, 0x60}, {0x76, 0x50}, {0x77, 0x48},
{0x3d, 0x92}, {0x3b, 0x00}, {0x04, 0x00}, {0xff, 0xff},
};
const uint8_t OV7670_QVGA[][2] PROGMEM =
{
{ 1, 0x42},
{ 320/16, 240/16},
{0b01000010, 0b00000100}, // Reset_Enable_N, 7|6|5|4|3|Vsync Invert|Hsync Invert|0
{0x3a, 0x0C}, {0x40, 0xC0}, {0x12, 0x10}, {0x0c, 0x00},
{0x3e, 0x00}, {0x70, 0x3A}, {0x71, 0x35}, {0x72, 0x11},
{0x73, 0xF0}, {0xa2, 0x02}, {0x11, 0x80}, {0x7a, 0x18},
{0x7b, 0x02}, {0x7c, 0x07}, {0x7d, 0x1F}, {0x7e, 0x49},
{0x7f, 0x5A}, {0x80, 0x6A}, {0x81, 0x79}, {0x82, 0x87},
{0x83, 0x94}, {0x84, 0x9F}, {0x85, 0xAF}, {0x86, 0xBB},
{0x87, 0xCF}, {0x88, 0xEE}, {0x89, 0xEE}, {0x13, 0xE0},
{0x00, 0x00}, {0x10, 0x00}, {0x0d, 0x00}, {0x24, 0x98},
{0x25, 0x63}, {0x26, 0xD3}, {0x2a, 0x00}, {0x2b, 0x00},
{0x2d, 0x00}, {0x13, 0xe5}, {0x13, 0xe7}, {0x1e, 0x30},
{0x74, 0x60}, {0x01, 0x80}, {0x02, 0x80}, {0x15, 0x10},
{0x4f, 0x40}, {0x50, 0x34}, {0x51, 0x0C}, {0x52, 0x17},
{0x53, 0x29}, {0x54, 0x40}, {0x57, 0x80}, {0x58, 0x1e},
{0x41, 0x10}, {0x75, 0x60}, {0x76, 0x50}, {0x77, 0x48},
{0x3d, 0x92}, {0x3b, 0x00}, {0x04, 0x00}, {0xff, 0xff},
};
#endif
На телефоне Android необходимо установить ArduinoTFT.apk (https://cdn.instructables.com/ORIG/FP9/HGMG/JUINB9LK/FP9HGMGJUINB9LK.apk) и разрешить приложению использовать Bluetooth и камеру.
В модуле Bluetooth необходимо установить скорость передачи данных 115200 (команда «AT + UART = 115200, 0, 0»). Это оптимальная скорость, с которой Arduino UNO удается получать и обрабатывать данные. (Теоретически вы можете увеличить скорость, оптимизировать прием и обработку данных, но для этого требуется больший объем оперативной памяти). Обратите внимание, что модуль Bluetooth подключается к порту отладки Arduino UNO. Таким образом, при работе с Bluetooth порт отладки недоступен. А перед программированием Arduino UNO (в комплекте с модулем Bluetooth) необходимо отключить модуль Bluetooth. И после программирования подключите его обратно.
Процесс сборки:









После включения питания экран TFT-экрана должен стать красным. Это означает готовность получать команды с телефона Android.



Выполните следующие операции на телефоне Android:
- Запустите приложение ArduinoTFT на телефоне Android
- Поверните телефон в горизонтальное положение
- Включите соединение Bluetooth, выберите обнаруженный модуль Bluetooth (HC-06)
На экране должны появиться два окна и четыре кнопки:
- В верхнем правом окне находится окно видоискателя камеры телефона
- Большое левое окно - полученные или отправленные изображения
Функции кнопок:
- Передача одного изображения с телефона Android на Arduino;
- Непрерывная передача изображений с телефона Android на Arduino;
- Передача одного изображения из Arduino на телефон Android;
- Непрерывная передача изображений с Arduino на телефон Android.
Размер изображения составляет 320x240 пикселей (2-5 кБ).

© digitrode.ru