Thư viện esp-camera là thành phần cốt lõi hỗ trợ tương tác với các module camera (như OV2640, OV3660) trên nền tảng ESP32 thông qua PlatformIO. Bài viết này sẽ đi sâu vào các cấu trúc dữ liệu cấu hình, bộ đệm khung hình và các hàm API quan trọng để điều khiển camera.
Cấu hình khởi tạo Camera
Việc thiết lập phần cứng và thông số kỹ thuật ban đầu được thực hiện thông qua cấu trúc camera_config_t. Cấu trúc này định nghĩa các chân GPIO, tần số xung clock, định dạng ảnh và cách quản lý bộ nhớ.
typedef struct {
int pin_pwdn; // Chân tắt nguồn camera
int pin_reset; // Chân reset camera
int pin_xclk; // Chân xung clock master
int pin_sccb_sda; // Chân dữ liệu I2C (SCCB)
int pin_sccb_scl; // Chân clock I2C (SCCB)
int pin_d7; // Chân dữ liệu D7
int pin_d6; // Chân dữ liệu D6
int pin_d5; // Chân dữ liệu D5
int pin_d4; // Chân dữ liệu D4
int pin_d3; // Chân dữ liệu D3
int pin_d2; // Chân dữ liệu D2
int pin_d1; // Chân dữ liệu D1
int pin_d0; // Chân dữ liệu D0
int pin_vsync; // Chân đồng bộ dọc
int pin_href; // Chân tham chiếu chiều ngang
int pin_pclk; // Chân clock pixel
int xclk_freq_hz; // Tần số XCLK (thường là 20MHz hoặc 10MHz)
ledc_timer_t ledc_timer; // Timer LEDC tạo XCLK
ledc_channel_t ledc_channel; // Kênh LEDC tạo XCLK
pixformat_t pixel_format; // Định dạng điểm ảnh (JPEG, RGB565, YUV422...)
framesize_t frame_size; // Kích thước khung hình (FRAMESIZE_QVGA, VGA...)
int jpeg_quality; // Chất lượng JPEG (0-63, thấp hơn là tốt hơn)
size_t fb_count; // Số lượng bộ đệm khung hình
camera_fb_location_t fb_location; // Vị trí bộ nhớ (DRAM hoặc PSRAM)
camera_grab_mode_t grab_mode; // Chế độ lấy khung hình
} camera_config_t;
Dưới đây là ví dụ về việc khởi tạo cấu trúc này cho module OV2640:
#define CAM_PIN_PWDN 32
#define CAM_PIN_RESET -1
#define CAM_PIN_XCLK 0
#define CAM_PIN_SDA 26
#define CAM_PIN_SCL 27
// ... định nghĩa các chân dữ liệu D0-D7 và VSYNC, HREF, PCLK ...
static camera_config_t esp32_cam_config = {
.pin_pwdn = CAM_PIN_PWDN,
.pin_reset = CAM_PIN_RESET,
.pin_xclk = CAM_PIN_XCLK,
.pin_sccb_sda = CAM_PIN_SDA,
.pin_sccb_scl = CAM_PIN_SCL,
.pin_d7 = 35,
.pin_d6 = 34,
.pin_d5 = 39,
.pin_d4 = 36,
.pin_d3 = 21,
.pin_d2 = 19,
.pin_d1 = 18,
.pin_d0 = 5,
.pin_vsync = 25,
.pin_href = 23,
.pin_pclk = 22,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_SVGA,
.jpeg_quality = 12,
.fb_count = 2,
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_LATEST
};
Quản lý dữ liệu khung hình
Sau khi khởi tạo, dữ liệu ảnh thu được sẽ được lưu trữ trong cấu trúc camera_fb_t. Cấu trúc này chứa con trỏ đến vùng nhớ dữ liệu nhị phân, độ dài dữ liệu cũng như các thông tin siêu dữ liệu như độ phân giải và định dạng.
typedef struct {
uint8_t * buf; // Con trỏ đến mảng byte chứa dữ liệu ảnh
size_t len; // Độ dài thực tế của dữ liệu (bytes)
size_t width; // Chiều rộng ảnh (pixels)
size_t height; // Chiều cao ảnh (pixels)
pixformat_t format; // Định dạng ảnh (ví dụ: JPEG)
struct timeval timestamp; // Thời gian bắt đầu lấy khung hình
} camera_fb_t;
Khi sử dụng, bạn cần lấy con trỏ này từ driver, xử lý dữ liệu và sau đó trả lại bộ đệm để hệ thống có thể tái sử dụng.
Các hàm API chính
1. Khởi tạo và giải phóng Driver
Hàm esp_camera_init() nhận tham số là cấu trúc cấu trúc đã điền ở trên để thiết lập phần cứng và cấp phát bộ nhớ. Hàm trả về ESP_OK nếu thành công.
esp_err_t err = esp_camera_init(&esp32_cam_config);
if (err != ESP_OK) {
Serial.printf("Lỗi khởi tạo camera: 0x%x", err);
return;
}
Để dừng hoạt động của camera và giải phóng tài nguyên, ta dùng esp_camera_deinit().
2. Chụp và trả về bộ đệm
Để chụp một khung hình (tương đương với việc lấy dữ liệu cảm biến lúc đó), ta sử dụng esp_camera_fb_get(). Hàm này trả về con trỏ camera_fb_t*. Sau khi xử lý xong dữ liệu trong buf, bắt buộc phải gọi esp_camera_fb_return() để tránh rò rỉ bộ nhớ.
camera_fb_t *picture = esp_camera_fb_get();
if (!picture) {
Serial.println("Không thể chụp ảnh");
return;
}
// Xử lý picture->buf với độ dài picture->len ở đây
// Ví dụ: gửi qua HTTP, lưu vào SD Card...
// Quan trọng: Trả lại bộ đệm
esp_camera_fb_return(picture);
3. Điều chỉnh thông số cảm biến
Hàm esp_camera_sensor_get() trả về con trỏ đến cấu trúc điều khiển cảm biến, cho phép thay đổi các thông số như độ phân giải, độ sáng, độ tương phản, hiệu ứng màu sắc ngay khi chương trình đang chạy.
sensor_t *s = esp_camera_sensor_get();
if (s) {
// Thay đổi độ phân giải
s->set_framesize(s, FRAMESIZE_VGA);
// Điều chỉnh chất lượng JPEG
s->set_quality(s, 10);
// Thiết lập lật ảnh
s->set_vflip(s, 1); // Lật dọc
s->set_hmirror(s, 1); // Lật ngang
// Điều chỉnh độ sáng (-2 đến 2)
s->set_brightness(s, 0);
}
4. Lưu trữ cấu hình (NVS)
Thư viện hỗ trợ lưu cấu hình hiện tại vào bộ nhớ NVS (Non-Volatile Storage) để giữ lại các cài đặt sau khi khởi động lại. Sử dụng esp_camera_save_to_nvs(key) để lưu và esp_camera_load_from_nvs(key) để tải.
// Lưu cài đặt hiện tại
esp_camera_save_to_nvs("my_cam_config");
// Tải cài đặt khi khởi động
if (esp_camera_load_from_nvs("my_cam_config") != ESP_OK) {
Serial.println("Không tìm thấy cấu hình lưu, dùng mặc định");
}
Ví dụ triển khai
Dưới đây là một lớp wrapper đơn giản để quản lý camera trong ứng dụng thực tế:
class CameraDriver {
private:
bool is_ready;
public:
CameraDriver() : is_ready(false) {}
bool start(const camera_config_t &config) {
if (esp_camera_init(&config) != ESP_OK) {
return false;
}
// Tải cài đặt nếu có
esp_camera_load_from_nvs("user_cfg");
sensor_t *s = esp_camera_sensor_get();
if (s) {
s->set_vflip(s, 1); // Mặc định lật dọc
}
is_ready = true;
return true;
}
bool captureAndSave() {
if (!is_ready) return false;
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) return false;
// Giả lập xử lý: in ra kích thước ảnh
Serial.printf("Đã chụp: %d bytes, %dx%d\n", fb->len, fb->width, fb->height);
// Trả lại bộ nhớ
esp_camera_fb_return(fb);
return true;
}
void saveSettings() {
esp_camera_save_to_nvs("user_cfg");
}
};
// Sử dụng trong main()
CameraDriver cam;
void setup() {
cam.start(esp32_cam_config);
}
void loop() {
cam.captureAndSave();
delay(5000); // Chụp mỗi 5 giây
}