You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
19 KiB
464 lines
19 KiB
# Zrób zdjęcie - Wio Terminal
|
|
|
|
W tej części lekcji dodasz kamerę do swojego Wio Terminal i zrobisz zdjęcia za jej pomocą.
|
|
|
|
## Sprzęt
|
|
|
|
Wio Terminal potrzebuje kamery.
|
|
|
|
Kamera, której użyjesz, to [ArduCam Mini 2MP Plus](https://www.arducam.com/product/arducam-2mp-spi-camera-b0067-arduino/). Jest to kamera o rozdzielczości 2 megapikseli, oparta na czujniku obrazu OV2640. Komunikuje się za pomocą interfejsu SPI, aby przechwytywać obrazy, i używa I2C do konfiguracji czujnika.
|
|
|
|
## Podłącz kamerę
|
|
|
|
ArduCam nie posiada gniazda Grove, zamiast tego łączy się z magistralami SPI i I2C za pomocą pinów GPIO na Wio Terminal.
|
|
|
|
### Zadanie - podłącz kamerę
|
|
|
|
Podłącz kamerę.
|
|
|
|

|
|
|
|
1. Piny na spodzie ArduCam muszą być podłączone do pinów GPIO na Wio Terminal. Aby łatwiej było znaleźć odpowiednie piny, przyklej naklejkę z oznaczeniami pinów GPIO, która jest dołączona do Wio Terminal:
|
|
|
|

|
|
|
|
1. Używając przewodów połączeniowych, wykonaj następujące połączenia:
|
|
|
|
| Pin ArduCAM | Pin Wio Terminal | Opis |
|
|
| ----------- | ---------------- | --------------------------------------- |
|
|
| CS | 24 (SPI_CS) | Wybór układu SPI |
|
|
| MOSI | 19 (SPI_MOSI) | Wyjście kontrolera SPI, wejście peryferyjne |
|
|
| MISO | 21 (SPI_MISO) | Wejście kontrolera SPI, wyjście peryferyjne |
|
|
| SCK | 23 (SPI_SCLK) | Zegar szeregowy SPI |
|
|
| GND | 6 (GND) | Masa - 0V |
|
|
| VCC | 4 (5V) | Zasilanie 5V |
|
|
| SDA | 3 (I2C1_SDA) | Dane szeregowe I2C |
|
|
| SCL | 5 (I2C1_SCL) | Zegar szeregowy I2C |
|
|
|
|

|
|
|
|
Połączenia GND i VCC dostarczają zasilanie 5V do ArduCam. Kamera działa na 5V, w przeciwieństwie do czujników Grove, które działają na 3V. Zasilanie pochodzi bezpośrednio z połączenia USB-C, które zasila urządzenie.
|
|
|
|
> 💁 W przypadku połączenia SPI etykiety pinów na ArduCam i nazwy pinów Wio Terminal używane w kodzie nadal korzystają ze starej konwencji nazewnictwa. Instrukcje w tej lekcji będą używać nowej konwencji nazewnictwa, z wyjątkiem przypadków, gdy nazwy pinów są używane w kodzie.
|
|
|
|
1. Teraz możesz podłączyć Wio Terminal do komputera.
|
|
|
|
## Zaprogramuj urządzenie, aby połączyć się z kamerą
|
|
|
|
Wio Terminal można teraz zaprogramować, aby korzystał z podłączonej kamery ArduCAM.
|
|
|
|
### Zadanie - zaprogramuj urządzenie, aby połączyć się z kamerą
|
|
|
|
1. Utwórz nowy projekt Wio Terminal w PlatformIO. Nazwij ten projekt `fruit-quality-detector`. Dodaj kod w funkcji `setup`, aby skonfigurować port szeregowy.
|
|
|
|
1. Dodaj kod do połączenia z WiFi, umieszczając dane logowania w pliku `config.h`. Nie zapomnij dodać wymaganych bibliotek do pliku `platformio.ini`.
|
|
|
|
1. Biblioteka ArduCam nie jest dostępna jako biblioteka Arduino, którą można zainstalować z pliku `platformio.ini`. Zamiast tego trzeba ją zainstalować ze źródła z ich strony GitHub. Możesz to zrobić na dwa sposoby:
|
|
|
|
* Klonując repozytorium z [https://github.com/ArduCAM/Arduino.git](https://github.com/ArduCAM/Arduino.git)
|
|
* Przechodząc do repozytorium na GitHub pod adresem [github.com/ArduCAM/Arduino](https://github.com/ArduCAM/Arduino) i pobierając kod jako plik zip za pomocą przycisku **Code**
|
|
|
|
1. Potrzebujesz tylko folderu `ArduCAM` z tego kodu. Skopiuj cały folder do folderu `lib` w swoim projekcie.
|
|
|
|
> ⚠️ Należy skopiować cały folder, aby kod znajdował się w `lib/ArduCam`. Nie kopiuj tylko zawartości folderu `ArduCam` do folderu `lib`, skopiuj cały folder.
|
|
|
|
1. Kod biblioteki ArduCam działa dla wielu typów kamer. Typ kamery, której chcesz użyć, jest konfigurowany za pomocą flag kompilatora - dzięki temu biblioteka jest jak najmniejsza, usuwając kod dla kamer, których nie używasz. Aby skonfigurować bibliotekę dla kamery OV2640, dodaj następujący kod na końcu pliku `platformio.ini`:
|
|
|
|
```ini
|
|
build_flags =
|
|
-DARDUCAM_SHIELD_V2
|
|
-DOV2640_CAM
|
|
```
|
|
|
|
Ustawia to dwie flagi kompilatora:
|
|
|
|
* `ARDUCAM_SHIELD_V2`, aby poinformować bibliotekę, że kamera znajduje się na płytce Arduino, znanej jako shield.
|
|
* `OV2640_CAM`, aby poinformować bibliotekę, aby uwzględniała tylko kod dla kamery OV2640.
|
|
|
|
1. Dodaj plik nagłówkowy do folderu `src`, nazwij go `camera.h`. Będzie on zawierał kod do komunikacji z kamerą. Dodaj do tego pliku następujący kod:
|
|
|
|
```cpp
|
|
#pragma once
|
|
|
|
#include <ArduCAM.h>
|
|
#include <Wire.h>
|
|
|
|
class Camera
|
|
{
|
|
public:
|
|
Camera(int format, int image_size) : _arducam(OV2640, PIN_SPI_SS)
|
|
{
|
|
_format = format;
|
|
_image_size = image_size;
|
|
}
|
|
|
|
bool init()
|
|
{
|
|
// Reset the CPLD
|
|
_arducam.write_reg(0x07, 0x80);
|
|
delay(100);
|
|
|
|
_arducam.write_reg(0x07, 0x00);
|
|
delay(100);
|
|
|
|
// Check if the ArduCAM SPI bus is OK
|
|
_arducam.write_reg(ARDUCHIP_TEST1, 0x55);
|
|
if (_arducam.read_reg(ARDUCHIP_TEST1) != 0x55)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Change MCU mode
|
|
_arducam.set_mode(MCU2LCD_MODE);
|
|
|
|
uint8_t vid, pid;
|
|
|
|
// Check if the camera module type is OV2640
|
|
_arducam.wrSensorReg8_8(0xff, 0x01);
|
|
_arducam.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
|
|
_arducam.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
|
|
if ((vid != 0x26) && ((pid != 0x41) || (pid != 0x42)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_arducam.set_format(_format);
|
|
_arducam.InitCAM();
|
|
_arducam.OV2640_set_JPEG_size(_image_size);
|
|
_arducam.OV2640_set_Light_Mode(Auto);
|
|
_arducam.OV2640_set_Special_effects(Normal);
|
|
delay(1000);
|
|
|
|
return true;
|
|
}
|
|
|
|
void startCapture()
|
|
{
|
|
_arducam.flush_fifo();
|
|
_arducam.clear_fifo_flag();
|
|
_arducam.start_capture();
|
|
}
|
|
|
|
bool captureReady()
|
|
{
|
|
return _arducam.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK);
|
|
}
|
|
|
|
bool readImageToBuffer(byte **buffer, uint32_t &buffer_length)
|
|
{
|
|
if (!captureReady()) return false;
|
|
|
|
// Get the image file length
|
|
uint32_t length = _arducam.read_fifo_length();
|
|
buffer_length = length;
|
|
|
|
if (length >= MAX_FIFO_SIZE)
|
|
{
|
|
return false;
|
|
}
|
|
if (length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// create the buffer
|
|
byte *buf = new byte[length];
|
|
|
|
uint8_t temp = 0, temp_last = 0;
|
|
int i = 0;
|
|
uint32_t buffer_pos = 0;
|
|
bool is_header = false;
|
|
|
|
_arducam.CS_LOW();
|
|
_arducam.set_fifo_burst();
|
|
|
|
while (length--)
|
|
{
|
|
temp_last = temp;
|
|
temp = SPI.transfer(0x00);
|
|
//Read JPEG data from FIFO
|
|
if ((temp == 0xD9) && (temp_last == 0xFF)) //If find the end ,break while,
|
|
{
|
|
buf[buffer_pos] = temp;
|
|
|
|
buffer_pos++;
|
|
i++;
|
|
|
|
_arducam.CS_HIGH();
|
|
}
|
|
if (is_header == true)
|
|
{
|
|
//Write image data to buffer if not full
|
|
if (i < 256)
|
|
{
|
|
buf[buffer_pos] = temp;
|
|
buffer_pos++;
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
_arducam.CS_HIGH();
|
|
|
|
i = 0;
|
|
buf[buffer_pos] = temp;
|
|
|
|
buffer_pos++;
|
|
i++;
|
|
|
|
_arducam.CS_LOW();
|
|
_arducam.set_fifo_burst();
|
|
}
|
|
}
|
|
else if ((temp == 0xD8) & (temp_last == 0xFF))
|
|
{
|
|
is_header = true;
|
|
|
|
buf[buffer_pos] = temp_last;
|
|
buffer_pos++;
|
|
i++;
|
|
|
|
buf[buffer_pos] = temp;
|
|
buffer_pos++;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
_arducam.clear_fifo_flag();
|
|
|
|
_arducam.set_format(_format);
|
|
_arducam.InitCAM();
|
|
_arducam.OV2640_set_JPEG_size(_image_size);
|
|
|
|
// return the buffer
|
|
*buffer = buf;
|
|
}
|
|
|
|
private:
|
|
ArduCAM _arducam;
|
|
int _format;
|
|
int _image_size;
|
|
};
|
|
```
|
|
|
|
Jest to kod niskopoziomowy, który konfiguruje kamerę za pomocą bibliotek ArduCam i pobiera obrazy w razie potrzeby za pomocą magistrali SPI. Kod ten jest bardzo specyficzny dla ArduCam, więc na tym etapie nie musisz się martwić, jak działa.
|
|
|
|
1. W pliku `main.cpp` dodaj następujący kod poniżej innych instrukcji `include`, aby dołączyć nowy plik i utworzyć instancję klasy kamery:
|
|
|
|
```cpp
|
|
#include "camera.h"
|
|
|
|
Camera camera = Camera(JPEG, OV2640_640x480);
|
|
```
|
|
|
|
Tworzy to obiekt `Camera`, zapisujący obrazy jako JPEG w rozdzielczości 640x480. Chociaż obsługiwane są wyższe rozdzielczości (do 3280x2464), klasyfikator obrazu działa na znacznie mniejszych obrazach (227x227), więc nie ma potrzeby przechwytywania i przesyłania większych obrazów.
|
|
|
|
1. Dodaj poniższy kod, aby zdefiniować funkcję konfigurującą kamerę:
|
|
|
|
```cpp
|
|
void setupCamera()
|
|
{
|
|
pinMode(PIN_SPI_SS, OUTPUT);
|
|
digitalWrite(PIN_SPI_SS, HIGH);
|
|
|
|
Wire.begin();
|
|
SPI.begin();
|
|
|
|
if (!camera.init())
|
|
{
|
|
Serial.println("Error setting up the camera!");
|
|
}
|
|
}
|
|
```
|
|
|
|
Funkcja `setupCamera` zaczyna od skonfigurowania pinu wyboru układu SPI (`PIN_SPI_SS`) jako wysokiego, czyniąc Wio Terminal kontrolerem SPI. Następnie uruchamia magistrale I2C i SPI. Na końcu inicjalizuje klasę kamery, która konfiguruje ustawienia czujnika kamery i upewnia się, że wszystko jest poprawnie podłączone.
|
|
|
|
1. Wywołaj tę funkcję na końcu funkcji `setup`:
|
|
|
|
```cpp
|
|
setupCamera();
|
|
```
|
|
|
|
1. Zbuduj i wgraj ten kod, a następnie sprawdź dane wyjściowe w monitorze szeregowym. Jeśli zobaczysz komunikat `Error setting up the camera!`, sprawdź okablowanie, aby upewnić się, że wszystkie przewody są podłączone do odpowiednich pinów na ArduCam i Wio Terminal, a wszystkie przewody są dobrze osadzone.
|
|
|
|
## Zrób zdjęcie
|
|
|
|
Wio Terminal można teraz zaprogramować tak, aby robił zdjęcie po naciśnięciu przycisku.
|
|
|
|
### Zadanie - zrób zdjęcie
|
|
|
|
1. Mikrokontrolery wykonują kod w sposób ciągły, więc trudno jest wywołać coś takiego jak zrobienie zdjęcia bez reakcji na czujnik. Wio Terminal ma przyciski, więc kamerę można skonfigurować tak, aby była wyzwalana jednym z przycisków. Dodaj poniższy kod na końcu funkcji `setup`, aby skonfigurować przycisk C (jeden z trzech przycisków na górze, ten najbliżej przełącznika zasilania).
|
|
|
|

|
|
|
|
```cpp
|
|
pinMode(WIO_KEY_C, INPUT_PULLUP);
|
|
```
|
|
|
|
Tryb `INPUT_PULLUP` zasadniczo odwraca wejście. Na przykład, normalnie przycisk wysyła niski sygnał, gdy nie jest naciśnięty, i wysoki sygnał, gdy jest naciśnięty. Gdy ustawiony jest na `INPUT_PULLUP`, wysyła wysoki sygnał, gdy nie jest naciśnięty, i niski sygnał, gdy jest naciśnięty.
|
|
|
|
1. Dodaj pustą funkcję reagującą na naciśnięcie przycisku przed funkcją `loop`:
|
|
|
|
```cpp
|
|
void buttonPressed()
|
|
{
|
|
|
|
}
|
|
```
|
|
|
|
1. Wywołaj tę funkcję w metodzie `loop`, gdy przycisk jest naciśnięty:
|
|
|
|
```cpp
|
|
void loop()
|
|
{
|
|
if (digitalRead(WIO_KEY_C) == LOW)
|
|
{
|
|
buttonPressed();
|
|
delay(2000);
|
|
}
|
|
|
|
delay(200);
|
|
}
|
|
```
|
|
|
|
Ten klucz sprawdza, czy przycisk jest naciśnięty. Jeśli jest naciśnięty, wywoływana jest funkcja `buttonPressed`, a pętla opóźnia się o 2 sekundy. Ma to na celu umożliwienie zwolnienia przycisku, aby długie naciśnięcie nie zostało zarejestrowane dwukrotnie.
|
|
|
|
> 💁 Przycisk na Wio Terminal jest ustawiony na `INPUT_PULLUP`, więc wysyła wysoki sygnał, gdy nie jest naciśnięty, i niski sygnał, gdy jest naciśnięty.
|
|
|
|
1. Dodaj poniższy kod do funkcji `buttonPressed`:
|
|
|
|
```cpp
|
|
camera.startCapture();
|
|
|
|
while (!camera.captureReady())
|
|
delay(100);
|
|
|
|
Serial.println("Image captured");
|
|
|
|
byte *buffer;
|
|
uint32_t length;
|
|
|
|
if (camera.readImageToBuffer(&buffer, length))
|
|
{
|
|
Serial.print("Image read to buffer with length ");
|
|
Serial.println(length);
|
|
|
|
delete(buffer);
|
|
}
|
|
```
|
|
|
|
Ten kod rozpoczyna przechwytywanie obrazu, wywołując `startCapture`. Sprzęt kamery nie działa w ten sposób, że zwraca dane na żądanie, zamiast tego wysyłasz instrukcję rozpoczęcia przechwytywania, a kamera działa w tle, aby przechwycić obraz, przekonwertować go na JPEG i zapisać w lokalnym buforze na samej kamerze. Wywołanie `captureReady` sprawdza, czy przechwytywanie obrazu zostało zakończone.
|
|
|
|
Po zakończeniu przechwytywania dane obrazu są kopiowane z bufora na kamerze do lokalnego bufora (tablicy bajtów) za pomocą wywołania `readImageToBuffer`. Długość bufora jest następnie wysyłana do monitora szeregowego.
|
|
|
|
1. Zbuduj i wgraj ten kod, a następnie sprawdź dane wyjściowe w monitorze szeregowym. Za każdym razem, gdy naciśniesz przycisk C, obraz zostanie przechwycony, a jego rozmiar zostanie wysłany do monitora szeregowego.
|
|
|
|
```output
|
|
Connecting to WiFi..
|
|
Connected!
|
|
Image captured
|
|
Image read to buffer with length 9224
|
|
Image captured
|
|
Image read to buffer with length 11272
|
|
```
|
|
|
|
Różne obrazy będą miały różne rozmiary. Są one kompresowane jako JPEG, a rozmiar pliku JPEG dla danej rozdzielczości zależy od tego, co znajduje się na obrazie.
|
|
|
|
> 💁 Kod ten znajdziesz w folderze [code-camera/wio-terminal](../../../../../4-manufacturing/lessons/2-check-fruit-from-device/code-camera/wio-terminal).
|
|
|
|
😀 Udało Ci się przechwycić obrazy za pomocą Wio Terminal.
|
|
|
|
## Opcjonalne - zweryfikuj obrazy z kamery za pomocą karty SD
|
|
|
|
Najłatwiejszym sposobem zobaczenia obrazów przechwyconych przez kamerę jest zapisanie ich na karcie SD w Wio Terminal, a następnie obejrzenie ich na komputerze. Wykonaj ten krok, jeśli masz wolną kartę microSD i gniazdo microSD w komputerze lub adapter.
|
|
|
|
Wio Terminal obsługuje tylko karty microSD o pojemności do 16 GB. Jeśli masz większą kartę SD, nie będzie działać.
|
|
|
|
### Zadanie - zweryfikuj obrazy z kamery za pomocą karty SD
|
|
|
|
1. Sformatuj kartę microSD jako FAT32 lub exFAT za pomocą odpowiednich aplikacji na komputerze (Disk Utility na macOS, File Explorer na Windows lub narzędzi wiersza poleceń w Linux).
|
|
|
|
1. Włóż kartę microSD do gniazda tuż poniżej przełącznika zasilania. Upewnij się, że jest całkowicie wsunięta, aż kliknie i pozostanie na miejscu. Może być konieczne użycie paznokcia lub cienkiego narzędzia.
|
|
|
|
1. Dodaj następujące instrukcje `include` na początku pliku `main.cpp`:
|
|
|
|
```cpp
|
|
#include "SD/Seeed_SD.h"
|
|
#include <Seeed_FS.h>
|
|
```
|
|
|
|
1. Dodaj następującą funkcję przed funkcją `setup`:
|
|
|
|
```cpp
|
|
void setupSDCard()
|
|
{
|
|
while (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI))
|
|
{
|
|
Serial.println("SD Card Error");
|
|
}
|
|
}
|
|
```
|
|
|
|
Funkcja ta konfiguruje kartę SD za pomocą magistrali SPI.
|
|
|
|
1. Wywołaj tę funkcję z funkcji `setup`:
|
|
|
|
```cpp
|
|
setupSDCard();
|
|
```
|
|
|
|
1. Dodaj poniższy kod powyżej funkcji `buttonPressed`:
|
|
|
|
```cpp
|
|
int fileNum = 1;
|
|
|
|
void saveToSDCard(byte *buffer, uint32_t length)
|
|
{
|
|
char buff[16];
|
|
sprintf(buff, "%d.jpg", fileNum);
|
|
fileNum++;
|
|
|
|
File outFile = SD.open(buff, FILE_WRITE );
|
|
outFile.write(buffer, length);
|
|
outFile.close();
|
|
|
|
Serial.print("Image written to file ");
|
|
Serial.println(buff);
|
|
}
|
|
```
|
|
|
|
Definiuje to globalną zmienną dla licznika plików. Jest ona używana do nazw plików obrazów, aby można było przechwycić wiele obrazów z rosnącymi nazwami plików - `1.jpg`, `2.jpg` i tak dalej.
|
|
|
|
Następnie definiuje funkcję `saveToSDCard`, która przyjmuje bufor danych bajtowych i długość bufora. Tworzona jest nazwa pliku przy użyciu licznika plików, a licznik plików jest zwiększany, aby przygotować się na kolejny plik. Dane binarne z bufora są następnie zapisywane do pliku.
|
|
|
|
1. Wywołaj funkcję `saveToSDCard` z funkcji `buttonPressed`. Wywołanie powinno być **przed** usunięciem bufora:
|
|
|
|
```cpp
|
|
Serial.print("Image read to buffer with length ");
|
|
Serial.println(length);
|
|
|
|
saveToSDCard(buffer, length);
|
|
|
|
delete(buffer);
|
|
```
|
|
|
|
1. Zbuduj i wgraj ten kod, a następnie sprawdź dane wyjściowe w monitorze szeregowym. Za każdym razem, gdy naciśniesz przycisk C, obraz zostanie przechwycony i zapisany na karcie SD.
|
|
|
|
```output
|
|
Connecting to WiFi..
|
|
Connected!
|
|
Image captured
|
|
Image read to buffer with length 16392
|
|
Image written to file 1.jpg
|
|
Image captured
|
|
Image read to buffer with length 14344
|
|
Image written to file 2.jpg
|
|
```
|
|
|
|
1. Wyłącz zasilanie karty microSD i wysuń ją, delikatnie wciskając i zwalniając, a karta wyskoczy. Może być konieczne użycie cienkiego narzędzia, aby to zrobić. Podłącz kartę microSD do komputera, aby obejrzeć obrazy.
|
|
|
|

|
|
💁 Może zająć kilka zdjęć, zanim balans bieli aparatu dostosuje się. Zauważysz to na podstawie kolorów uchwyconych zdjęć, pierwsze kilka może wyglądać na nieprawidłowe. Zawsze możesz obejść ten problem, zmieniając kod tak, aby uchwycić kilka zdjęć, które są ignorowane w funkcji `setup`.
|
|
|
|
|
|
**Zastrzeżenie**:
|
|
Ten dokument został przetłumaczony za pomocą usługi tłumaczenia AI [Co-op Translator](https://github.com/Azure/co-op-translator). Chociaż dokładamy wszelkich starań, aby tłumaczenie było precyzyjne, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w jego rodzimym języku powinien być uznawany za autorytatywne źródło. W przypadku informacji o kluczowym znaczeniu zaleca się skorzystanie z profesjonalnego tłumaczenia przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia. |