Skip to content

Updates I made to make it run + documentation/debugging #11

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 7 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ Project Status:
- [x] Can set resolution
- [ ] Can set remaining filters and modes
- [ ] Able to set multiple adjustments at the same time ([see issue#3](https://github.com/CoreElectronics/CE-Arducam-MicroPython/issues))
- [ ] Class moved to separate file
- [x] Class moved to separate file
- [ ] Burst read - decrease time to save photo
- [ ] Set SPI Speed higher - decrease time to save photo - Recommended speed from ArduCam 800000 baud, need to implement a check on init
- [ ] Confirm a micro SD card can use the same SPI bus (Micropython compatibility) - requires camera to release SPI bus, bulk reading/writing into bytearray would speed this up
- [ ] Confirm working with the latest Micropython version
- [x] Confirm working with the latest Micropython version - Version 1.24 tested
- [ ] Filemanager also handles subfolders for images - Requires examples
- [ ] Confirm that different file formats output correctly (RGB=BMP, YGV?)
- [ ] Confirm that pixel RGB values can be extrapolated from BMP format for machine learning applications

The Camera library can be created by extracting the 'Camera' class from the [main.py](https://github.com/CoreElectronics/CE-Arducam-MicroPython/blob/main/main.py) file.
The Camera library can be created by extracting the 'Camera' class from the [camera.py](https://github.com/CoreElectronics/CE-Arducam-MicroPython/blob/main/camera.py) file.

# License
This project is open source - please review the LICENSE.md file for further licensing information.
Expand Down
198 changes: 117 additions & 81 deletions camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ class Camera:
# For 5MP startup routine
WHITE_BALANCE_WAIT_TIME_MS = 500

# Debug Mode
DEBUG_MODE = False


# User callable functions
## Main functions
Expand All @@ -264,6 +267,7 @@ class Camera:
def __init__(self, spi_bus, cs, skip_sleep=False, debug_information=False):
self.cs = cs
self.spi_bus = spi_bus
self.DEBUG_MODE = debug_information

self._write_reg(self.CAM_REG_SENSOR_RESET, self.CAM_SENSOR_RESET_ENABLE) # Reset camera
self._wait_idle()
Expand Down Expand Up @@ -292,14 +296,19 @@ def __init__(self, spi_bus, cs, skip_sleep=False, debug_information=False):
self.image_buffer = bytearray(self.BUFFER_MAX_LENGTH)
self.valid_image_buffer = 0

self.camera_idx = 'NOT DETECTED'
#This seems to be overwriting the value so I am removing it set this before self._get_sensor_config()?
#self.camera_idx = 'NOT DETECTED'


# Tracks the AWB warmup time
self.start_time = utime.ticks_ms()
if debug_information:
print('Camera version =', self.camera_idx)
if self.camera_idx == '3MP':
#fifo length is sometimes a broken value (5 to 8 megabytes) and causes program to fail this helps
utime.sleep_ms(self.WHITE_BALANCE_WAIT_TIME_MS)
self._wait_idle()

self.startup_routine_3MP()

if self.camera_idx == '5MP' and skip_sleep == False:
Expand All @@ -308,11 +317,11 @@ def __init__(self, spi_bus, cs, skip_sleep=False, debug_information=False):

def startup_routine_3MP(self):
# Leave the shutter open for some time seconds (i.e. take a few photos without saving)
print('Running 3MP startup routine')
if self.DEBUG_MODE: print('Running 3MP startup routine')
self.capture_jpg()
self.saveJPG('dummy_image.jpg')
uos.remove('dummy_image.jpg')
print('complete')
if self.DEBUG_MODE: print('Finished 3MP startup routine')

'''
Issue warning if the filepath doesnt end in .jpg (Blank) and append
Expand All @@ -323,40 +332,44 @@ def capture_jpg(self):
if (utime.ticks_diff(utime.ticks_ms(), self.start_time) <= self.WHITE_BALANCE_WAIT_TIME_MS) and self.camera_idx == '5MP':
print('Please add a ', self.WHITE_BALANCE_WAIT_TIME_MS, 'ms delay to allow for white balance to run')
else:
# print('Starting capture JPG')

if self.DEBUG_MODE: print('Entered capture_jpg')

# JPG, bmp ect
# TODO: PROPERTIES TO CONFIGURE THE PIXEL FORMAT
if (self.old_pixel_format != self.current_pixel_format) or self.run_start_up_config:
self.old_pixel_format = self.current_pixel_format
self._write_reg(self.CAM_REG_FORMAT, self.current_pixel_format) # Set to capture a jpg
self._wait_idle()
# print('old',self.old_resolution,'new',self.current_resolution_setting)

if self.DEBUG_MODE: print('old_resolution: ',self.old_resolution,'new_resolution: ',self.current_resolution_setting)

# TODO: PROPERTIES TO CONFIGURE THE RESOLUTION
if (self.old_resolution != self.current_resolution_setting) or self.run_start_up_config:
self.old_resolution = self.current_resolution_setting
self._write_reg(self.CAM_REG_CAPTURE_RESOLUTION, self.current_resolution_setting)
# print('setting res', self.current_resolution_setting)
if self.DEBUG_MODE: print('setting resolution: ', self.current_resolution_setting)
self._wait_idle()
self.run_start_up_config = False

# Start capturing the photo
self._set_capture()
# print('capture jpg complete')
if self.DEBUG_MODE: print('Finished capture_jpg')


# TODO: After reading the camera data clear the FIFO and reset the camera (so that the first time read can be used)
def saveJPG(self,filename):
headflag = 0
print('Saving image, please dont remove power')
# print('rec len:', self.received_length)
if self.DEBUG_MODE: print('rec len:', self.received_length)

image_data = 0x00
image_data_next = 0x00

image_data_int = 0x00
image_data_next_int = 0x00

print(self.received_length)
#print(self.received_length)

while(self.received_length):

Expand All @@ -365,44 +378,60 @@ def saveJPG(self,filename):

image_data_next = self._read_byte()
image_data_next_int = int.from_bytes(image_data_next, 1) # TODO: CHANGE TO READ n BYTES
if headflag == 1:
jpg_to_write.write(image_data_next)

if (image_data_int == 0xff) and (image_data_next_int == 0xd8):
# TODO: Save file to filename
# print('start of file')
headflag = 1
jpg_to_write = open(filename,'ab')
jpg_to_write.write(image_data)
jpg_to_write.write(image_data_next)

if (image_data_int == 0xff) and (image_data_next_int == 0xd9):
# print('TODO: Save and close file?')
headflag = 0
if headflag == 0:
if (image_data_int == 0xff) and (image_data_next_int == 0xd8):
# TODO: Save file to filename
# print('start of file', self.received_length)
headflag = 1
jpg_to_write = open(filename,'ab')
jpg_to_write.write(image_data)
jpg_to_write.write(image_data_next)
else:
jpg_to_write.write(image_data_next)
jpg_to_write.close()





def save_JPG_burst(self):
headflag = 0
print('Saving image, please dont remove power')

image_data = 0x00
image_data_next = 0x00

image_data_int = 0x00
image_data_next_int = 0x00

print(self.received_length)

while(self.received_length):
self._burst_read_FIFO()

start_bytes = self.image_buffer[0]
end_bytes = self.image_buffer[self.valid_image_buffer-1]
if (image_data_int == 0xff) and (image_data_next_int == 0xd9):
# print('close file', self.received_length)
jpg_to_write.close()
break;


#resolution = '640x480'
#saveJPG 31.373s
#save_JPG_burst 0.8679999s
#save_JPG_burst did not work with resolution = '320X240' not sure why, output file is empty

def save_JPG_burst(self,filename):
# print('start of file', self.received_length)
jpg_to_write = open(filename,'ab')

recv_len = self.received_length

self.cs.off()
self.spi_bus.write(bytes([self.BURST_FIFO_READ]))

# Throw away first byte on first read
data = self.spi_bus.read(1)

inx = 0

while recv_len > 0:
last_byte = self.image_buffer[self.BUFFER_MAX_LENGTH - 1]
# print('last', last_byte)
self.spi_bus.readinto(self.image_buffer)
recv_len -= self.BUFFER_MAX_LENGTH
# if self.image_buffer[0] == 0xff and self.image_buffer[1] == 0xd8:
# print('start of jpeg image')
# print('buff', self.image_buffer)
inx = self.image_buffer.find(b'\xff\xd9')
if inx >= 0:
jpg_to_write.write(self.image_buffer[:inx+2])
# print(self.image_buffer[:inx+2])
break
elif last_byte == 0xff and self.image_buffer[0] == 0xd9:
jpg_to_write.write(b'\xd9')
# print('corner case termination')
break
else:
jpg_to_write.write(self.image_buffer)


# def _read_byte(self):
Expand All @@ -415,34 +444,34 @@ def save_JPG_burst(self):
# return data


def _burst_read_FIFO(self):
#compute how many bytes to read
burst_read_length = self.BUFFER_MAX_LENGTH # Default to max length
if self.received_length < self.BUFFER_MAX_LENGTH:
burst_read_length = self.received_length
current_buffer_byte = 0
# def _burst_read_FIFO(self):
# #compute how many bytes to read
# burst_read_length = self.BUFFER_MAX_LENGTH # Default to max length
# if self.received_length < self.BUFFER_MAX_LENGTH:
# burst_read_length = self.received_length
# current_buffer_byte = 0

self.cs.off()
self.spi_bus.write(bytes([self.BURST_FIFO_READ]))
# self.cs.off()
# self.spi_bus.write(bytes([self.BURST_FIFO_READ]))

data = self.spi_bus.read(1)
# Throw away first byte on first read
if self.first_burst_fifo == True:
self.image_buffer[current_buffer_byte] = data
current_buffer_byte += 1
burst_read_length -= 1
print('first burst read')
# data = self.spi_bus.read(1)
# # Throw away first byte on first read
# if self.first_burst_fifo == True:
# self.image_buffer[current_buffer_byte] = data
# current_buffer_byte += 1
# burst_read_length -= 1
# print('first burst read')


while burst_read_length:
data = self.spi_bus.read(1) # Read from camera
self.image_buffer[current_buffer_byte] = data # write to buffer
current_buffer_byte += 1
burst_read_length -= 1
# while burst_read_length:
# data = self.spi_bus.read(1) # Read from camera
# self.image_buffer[current_buffer_byte] = data # write to buffer
# current_buffer_byte += 1
# burst_read_length -= 1

self.cs.on()
self.received_length -= burst_read_length
self.valid_image_buffer = burst_read_length
# self.cs.on()
# self.received_length -= burst_read_length
# self.valid_image_buffer = burst_read_length


@property
Expand Down Expand Up @@ -565,25 +594,35 @@ def _start_capture(self):
self._write_reg(self.ARDUCHIP_FIFO, self.FIFO_START_MASK)

def _set_capture(self):
# print('a1')
if self.DEBUG_MODE: print('Entered _set_capture')
self._clear_fifo_flag()
self._wait_idle()
self._start_capture()
# print('a2')
if self.DEBUG_MODE: print('fifo flag cleared, started _start_capture, waiting for CAP_DONE_MASK')
while (int(self._get_bit(self.ARDUCHIP_TRIG, self.CAP_DONE_MASK)) == 0):
# print(self._get_bit(self.ARDUCHIP_TRIG, self.CAP_DONE_MASK))
if self.DEBUG_MODE: print("ARDUCHIP_TRIG register, CAP_DONE_MASK:", self._get_bit(self.ARDUCHIP_TRIG, self.CAP_DONE_MASK))
sleep_ms(200)
# print('a3')
if self.DEBUG_MODE:print('finished waiting for _start_capture')

#_read_fifo_length() was giving imposible value tried adding wait, did not seem to fix it
self._wait_idle()

self.received_length = self._read_fifo_length()
self.total_length = self.received_length
self.burst_first_flag = False
# print('a4')
if self.DEBUG_MODE: print('fifo length has been read')

def _read_fifo_length(self): # TODO: CONFIRM AND SWAP TO A 3 BYTE READ
len1 = int.from_bytes(self._read_reg(self.FIFO_SIZE1),1)
len2 = int.from_bytes(self._read_reg(self.FIFO_SIZE2),1)
len3 = int.from_bytes(self._read_reg(self.FIFO_SIZE3),1)
# print(len1,len2,len3)
if self.DEBUG_MODE: print('Entered _read_fifo_length')
len1 = int.from_bytes(self._read_reg(self.FIFO_SIZE1),1) #0x45
len2 = int.from_bytes(self._read_reg(self.FIFO_SIZE2),1) #0x46
len3 = int.from_bytes(self._read_reg(self.FIFO_SIZE3),1) #0x47
if self.DEBUG_MODE:
print("fifo length bytes (int):")
print(len1,len2,len3)
if ((((len3 << 16) | (len2 << 8) | len1) & 0xffffff)>5000000):
print("Error fifo length is too long >5MB Size: ", (((len3 << 16) | (len2 << 8) | len1) & 0xffffff))
print("Arducam possibly did not take a picture and is returning garbage data")
return ((len3 << 16) | (len2 << 8) | len1) & 0xffffff

def _get_sensor_config(self):
Expand Down Expand Up @@ -641,6 +680,3 @@ def _wait_idle(self):
def _get_bit(self, addr, bit):
data = self._read_reg(addr);
return int.from_bytes(data, 1) & bit;



40 changes: 33 additions & 7 deletions examples/debug_information.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
spi = SPI(0,sck=Pin(18), miso=Pin(16), mosi=Pin(19), baudrate=8000000)
from machine import Pin, SPI
from camera import *
#Baud 8000000

'''
#################### PINOUT ####################
Camera pin - Pico Pin
VCC - 3V3 - red
GND - GND - black
SPI - 0
SCK - GP18 - white
MISO - RX - GP16 - brown
MOSI - TX - GP19 - yellow
CS - GP17 - orange

Camera pin - ESP32 S3
VCC - 3V3 - red
GND - GND - black
SPI - 2
SCK - GP12 - white
MISO - RX - GP13 - brown
MOSI - TX - GP11 - yellow
CS - GP17 - orange
'''

spi = SPI(2,sck=Pin(12), miso=Pin(13), mosi=Pin(11), baudrate=100000)
cs = Pin(17, Pin.OUT)

# button = Pin(15, Pin.IN,Pin.PULL_UP)
onboard_LED = Pin(25, Pin.OUT)
onboard_LED = Pin(48, Pin.OUT)

cam = Camera(spi, cs, debug_information=True)
'''
RESOLUTION_160X120 = 0X00
RESOLUTION_320X240 = 0X01
RESOLUTION_640X480 = 0X02
RESOLUTION_1280X720 = 0X03
'''
RESOLUTION_320X240 = 0X01
RESOLUTION_640X480 = 0X02
RESOLUTION_1280X720 = 0X03
'''


cam.resolution = '640X480'
cam.resolution = '320X240'
cam.resolution

start_time_capture = utime.ticks_ms()

Expand Down
3 changes: 2 additions & 1 deletion examples/filemanager_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import uos
import ujson

#old code that is now also inside of camera.py

class FileManager:
def __init__(self, file_manager_name='filemanager.log'):
Expand Down Expand Up @@ -56,4 +57,4 @@ def save_manager_file(self):


fm = FileManager()
print(fm.new_jpg_filename('image'))
print(fm.new_jpg_fn('image'))
Loading