Created
June 19, 2017 22:31
-
-
Save hfiennes/fc0499672df280bbdf603eadcfef6a84 to your computer and use it in GitHub Desktop.
Arducam Mini 2MP driver for imp (not fully tested in this form, YMMV)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const css = @" | |
body { | |
background: #35a8dd; | |
color: white; | |
font-family: ""Helvetica Neue"", ""Helvetica"",""sans""; | |
} | |
" | |
http.onrequest(function(req,res) { | |
if (req.path == "/camera.jpg") { | |
res.header("Content-Type", "image/jpeg"); | |
res.send(200, image); | |
} else if (req.path == "/css.css") { | |
res.header("Content-Type", "text/css"); | |
res.send(200, css); | |
} | |
}); | |
jpeg_buffer <- null | |
jpeg_startat <- 0; | |
image <- null | |
device.on("jpeg_start", function(size) { | |
jpeg_buffer = blob(size); | |
jpeg_startat = time(); | |
}); | |
device.on("jpeg_chunk", function(v) { | |
// check we've not got some barf from a previous boot | |
if (jpeg_buffer == null) return; | |
local offset = v[0]; | |
local b = v[1]; | |
for(local i = offset; i < (offset+b.len()); i++) { | |
if(i < jpeg_buffer.len()) { | |
jpeg_buffer[i] = b[i-offset]; | |
} | |
} | |
}); | |
device.on("jpeg_end", function(v) { | |
// check we've not got some barf from a previous boot | |
if (jpeg_buffer == null) return; | |
// copy last JPEG to web server blob | |
image = jpeg_buffer | |
server.log(format("Agent: JPEG Received (%d bytes) at rate of %.2fkB/s",image.len(), (image.len()/1024.0)/(time()-jpeg_startat))); | |
server.log(format("Agent memory remaining: %d bytes", imp.getmemoryfree())); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2013-2016 Electric Imp | |
// This file is licensed under the MIT License | |
// http://opensource.org/licenses/MIT | |
// Imp firmware for Arudcam Mini 2MP | |
// Shield is Based on OV2640 Camera Module | |
// http://www.arducam.com/tag/arducam-mini/ | |
class Camera { | |
// These sensor register sets are taken from the ArduCAM source, and reorganized a bit to expose commonalities | |
static OV2640_JPEG_INIT = | |
"\xff\x00\x2c\xff\x2e\xdf\xff\x01\x3c\x32\x11\x04\x09\x02\x04\x28\x13\xe5\x14\x48\x2c\x0c\x33\x78\x3a\x33\x3b\xfB\x3e\x00\x43\x11\x16\x10\x39\x92"+ | |
"\x35\xda\x22\x1a\x37\xc3\x23\x00\x34\xc0\x36\x1a\x06\x88\x07\xc0\x0d\x87\x0e\x41\x4c\x00\x48\x00\x5B\x00\x42\x03\x4a\x81\x21\x99\x24\x40\x25\x38"+ | |
"\x26\x82\x5c\x00\x63\x00\x61\x70\x62\x80\x7c\x05\x20\x80\x28\x30\x6c\x00\x6d\x80\x6e\x00\x70\x02\x71\x94\x73\xc1\x12\x40\x17\x11\x18\x43\x19\x00"+ | |
"\x1a\x4b\x32\x09\x37\xc0\x4f\x60\x50\xa8\x6d\x00\x3d\x38\x46\x3f\x4f\x60\x0c\x3c\xff\x00\xe5\x7f\xf9\xc0\x41\x24\xe0\x14\x76\xff\x33\xa0\x42\x20"+ | |
"\x43\x18\x4c\x00\x87\xd5\x88\x3f\xd7\x03\xd9\x10\xd3\x82\xc8\x08\xc9\x80\x7c\x00\x7d\x00\x7c\x03\x7d\x48\x7d\x48\x7c\x08\x7d\x20\x7d\x10\x7d\x0e"+ | |
"\x90\x00\x91\x0e\x91\x1a\x91\x31\x91\x5a\x91\x69\x91\x75\x91\x7e\x91\x88\x91\x8f\x91\x96\x91\xa3\x91\xaf\x91\xc4\x91\xd7\x91\xe8\x91\x20\x92\x00"+ | |
"\x93\x06\x93\xe3\x93\x05\x93\x05\x93\x00\x93\x04\x93\x00\x93\x00\x93\x00\x93\x00\x93\x00\x93\x00\x93\x00\x96\x00\x97\x08\x97\x19\x97\x02\x97\x0c"+ | |
"\x97\x24\x97\x30\x97\x28\x97\x26\x97\x02\x97\x98\x97\x80\x97\x00\x97\x00\xc3\xed\xa4\x00\xa8\x00\xc5\x11\xc6\x51\xbf\x80\xc7\x10\xb6\x66\xb8\xa5"+ | |
"\xb7\x64\xb9\x7c\xb3\xaf\xb4\x97\xb5\xff\xb0\xc5\xb1\x94\xb2\x0f\xc4\x5c\xc0\x64\xc1\x4b\x8c\x00\x86\x3d\x50\x00\x51\xc8\x52\x96\x53\x00\x54\x00"+ | |
"\x55\x00\x5a\xc8\x5b\x96\x5c\x00\xd3\x00\xc3\xed\x7f\x00\xda\x00\xe5\x1f\xe1\x67\xe0\x00\xdd\x7f\x05\x00\x12\x40\xd3\x04\xc0\x16\xc1\x12\x8c\x00"+ | |
"\x86\x3d\x50\x00\x51\x2c\x52\x24\x53\x00\x54\x00\x55\x00\x5a\x2c\x5b\x24\x5c\x00"; | |
static OV2640_YUV422 = | |
"\xff\x00\x05\x00\xda\x10\xd7\x03\xdf\x00\x33\x80\x3c\x40\xe1\x77\x00\x00"; | |
static OV2640_JPEG = | |
"\xe0\x14\xe1\x77\xe5\x1f\xd7\x03\xda\x10\xe0\x00\xFF\x01\x04\x08"; | |
static OV2640_160x120_JPEG = | |
"\xff\x01\x12\x40\x17\x11\x18\x43\x19\x00\x1a\x4b\x32\x09\x4f\xca\x50\xa8\x5a\x23\x6d\x00\x39\x12\x35\xda\x22\x1a\x37\xc3\x23\x00\x34\xc0\x36\x1a"+ | |
"\x06\x88\x07\xc0\x0d\x87\x0e\x41\x4c\x00\xff\x00\xe0\x04\xc0\x64\xc1\x4b\x86\x35"+ | |
"\x50\x92"+ | |
"\x51\xc8\x52\x96\x53\x00\x54\x00\x55\x00\x57\x00"+ | |
"\x5a\x28\x5b\x1e\x5c\x00\xe0\x00"; | |
static OV2640_176x144_JPEG = | |
"\xff\x01\x12\x40\x17\x11\x18\x43\x19\x00\x1a\x4b\x32\x09\x4f\xca\x50\xa8\x5a\x23\x6d\x00\x39\x12\x35\xda\x22\x1a\x37\xc3\x23\x00\x34\xc0\x36\x1a"+ | |
"\x06\x88\x07\xc0\x0d\x87\x0e\x41\x4c\x00\xff\x00\xe0\x04\xc0\x64\xc1\x4b"+ | |
"\x86\x35\x50\x92"+ | |
"\x51\xc8\x52\x96\x53\x00\x54\x00\x55\x00\x57\x00"+ | |
"\x5a\x2c\x5b\x24\x5c\x00"+ | |
"\xe0\x00"; | |
static OV2640_320x240_JPEG = | |
"\xff\x01\x12\x40\x17\x11\x18\x43\x19\x00\x1a\x4b\x32\x09\x4f\xca\x50\xa8\x5a\x23\x6d\x00\x39\x12\x35\xda\x22\x1a\x37\xc3\x23\x00\x34\xc0\x36\x1a"+ | |
"\x06\x88\x07\xc0\x0d\x87\x0e\x41\x4c\x00\xff\x00\xe0\x04\xc0\x64\xc1\x4b"+ | |
"\x86\x35\x50\x89"+ | |
"\x51\xc8\x52\x96\x53\x00\x54\x00\x55\x00\x57\x00"+ | |
"\x5a\x50\x5b\x3c\x5c\x00"+ | |
"\xe0\x00"; | |
static OV2640_352x288_JPEG = | |
"\xff\x01\x12\x40\x17\x11\x18\x43\x19\x00\x1a\x4b\x32\x09\x4f\xca\x50\xa8\x5a\x23\x6d\x00\x39\x12\x35\xda\x22\x1a\x37\xc3\x23\x00\x34\xc0\x36\x1a"+ | |
"\x06\x88\x07\xc0\x0d\x87\x0e\x41\x4c\x00\xff\x00\xe0\x04\xc0\x64\xc1\x4b"+ | |
"\x86\x35\x50\x89"+ | |
"\x51\xc8\x52\x96\x53\x00\x54\x00\x55\x00\x57\x00"+ | |
"\x5a\x58\x5b\x48\x5c\x00"+ | |
"\xe0\x00"; | |
static OV2640_640x480_JPEG = | |
"\xff\x01\x11\x01\x12\x00\x17\x11\x18\x75\x32\x36\x19\x01\x1a\x97\x03\x0f\x37\x40\x4f\xbb\x50\x9c\x5a\x57\x6d\x80\x3d\x34\x39\x02\x35\x88\x22\x0a"+ | |
"\x37\x40\x34\xa0\x06\x02\x0d\xb7\x0e\x01\xff\x00\xe0\x04\xc0\xc8\xc1\x96\x51\x90\x52\x2c\x53\x00\x54\x00\x55\x88\x57\x00"+ | |
"\x86\x3d\x50\x89"+ | |
"\x5a\xa0\x5b\x78\x5c\x00"+ // OUTW=640, OUTH=480 | |
"\xd3\x04\xe0\x00"; | |
static OV2640_800x600_JPEG = | |
"\xff\x01\x11\x01\x12\x00\x17\x11\x18\x75\x32\x36\x19\x01\x1a\x97\x03\x0f\x37\x40\x4f\xbb\x50\x9c\x5a\x57\x6d\x80\x3d\x34\x39\x02\x35\x88\x22\x0a"+ | |
"\x37\x40\x34\xa0\x06\x02\x0d\xb7\x0e\x01\xff\x00\xe0\x04\xc0\xc8\xc1\x96\x51\x90\x52\x2c\x53\x00\x54\x00\x55\x88\x57\x00"+ | |
"\x86\x35\x50\x89"+ | |
"\x5a\xc8\x5b\x96\x5c\x00"+ // OUTW=800, OUTH=600 | |
"\xd3\x02\xe0\x00"; | |
static OV2640_1024x768_JPEG = | |
"\xff\x01\x11\x01\x12\x00\x17\x11\x18\x75\x32\x36\x19\x01\x1a\x97\x03\x0f\x37\x40\x4f\xbb\x50\x9c\x5a\x57\x6d\x80\x3d\x34\x39\x02\x35\x88\x22\x0a"+ | |
"\x37\x40\x34\xa0\x06\x02\x0d\xb7\x0e\x01\xff\x00\xe0\x04\xc0\xc8\xc1\x96\x51\x90\x52\x2c\x53\x00\x54\x00\x55\x88\x57\x00"+ | |
"\x86\x3d\x50\x00"+ | |
"\x5a\x00\x5b\xc0\x5c\x01"+ // OUTW=1024, OUTH=768 | |
"\xd3\x02\xe0\x00"; | |
static OV2640_1280x960_JPEG = | |
"\xff\x01\x11\x01\x12\x00\x17\x11\x18\x75\x32\x36\x19\x01\x1a\x97\x03\x0f\x37\x40\x4f\xbb\x50\x9c\x5a\x57\x6d\x80\x3d\x34\x39\x02\x35\x88\x22\x0a"+ | |
"\x37\x40\x34\xa0\x06\x02\x0d\xb7\x0e\x01\xff\x00\xe0\x04\xc0\xc8\xc1\x96\x51\x90\x52\x2c\x53\x00\x54\x00\x55\x88\x57\x00"+ | |
"\x86\x3d\x50\x00"+ | |
"\x5a\x40\x5b\xf0\x5c\x01"+ // OUTW=1280, OUTH=960 | |
"\xd3\x02\xe0\x00"; | |
static OV2640_1600x1200_JPEG = | |
"\xff\x01\x11\x01\x12\x00\x17\x11\x18\x75\x32\x36\x19\x01\x1a\x97\x03\x0f\x37\x40\x4f\xbb\x50\x9c\x5a\x57\x6d\x80\x3d\x34\x39\x02\x35\x88\x22\x0a"+ | |
"\x37\x40\x34\xa0\x06\x02\x0d\xb7\x0e\x01\xff\x00\xe0\x04\xc0\xc8\xc1\x96\x51\x90\x52\x2c\x53\x00\x54\x00\x55\x88\x57\x00"+ | |
"\x86\x3d\x50\x00"+ | |
"\x5a\x90\x5b\x2c\x5c\x05"+ // OUTW=1600, OUTH=1200 | |
"\xd3\x02\xe0\x00"; | |
static ARDUCHIP_FIFO = 0x04 //FIFO and I2C control | |
static FIFO_CLEAR_MASK = 0x01 | |
static FIFO_START_MASK = 0x02 | |
static FIFO_RDPTR_RST_MASK = 0x10 | |
static FIFO_WRPTR_RST_MASK = 0x20 | |
static ARDUCHIP_GPIO = 0x06 //GPIO Write Register | |
static BURST_FIFO_READ = 0x3c //Burst FIFO read operation | |
static SINGLE_FIFO_READ = 0x3d //Single FIFO read operation | |
static ARDUCHIP_REV = 0x40 //ArduCHIP revision | |
static VER_LOW_MASK = 0x3f | |
static VER_HIGH_MASK = 0xc0 | |
static ARDUCHIP_TRIG = 0x41 //Trigger source | |
static VSYNC_MASK = 0x01 | |
static SHUTTER_MASK = 0x02 | |
static CAP_DONE_MASK = 0x08 | |
static FIFO_SIZE1 = 0x42 //Camera write FIFO size[7:0] for burst to read | |
static FIFO_SIZE2 = 0x43 //Camera write FIFO size[15:8] | |
static FIFO_SIZE3 = 0x44 //Camera write FIFO size[18:16] | |
static ARDUCHIP_TEST1 = 0x00 //Test Register | |
// set by constructor | |
i2c = null; | |
spi = null; | |
cs_l = null; | |
// size of data chunks to send agent. Large multiple of 8. | |
static CHUNK_SIZE = 8192; | |
// next chunk to send | |
chunk_next = 0; | |
// number of chunks to send | |
chunk_count = 0; | |
/************************************************************************** | |
* | |
* Constructor takes in a pre-configured I2C interface and SPI interface, and resets the camera | |
* | |
*************************************************************************/ | |
constructor(_spi, _cs_l, _i2c) { | |
spi = _spi; | |
cs_l = _cs_l; | |
i2c = _i2c; | |
// the imp's SPI interface does not implicitly include a CS pin | |
// configure a GPIO to use as the chip select (active low) | |
cs_l.configure(DIGITAL_OUT); | |
cs_l.write(1); | |
reset(); | |
}; | |
function _spi_write(address, value) { | |
//server.log(format("Writing: 0x%02x to 0x%02x",value,address)); | |
cs_l.write(0); | |
spi.write(address.tochar() + value.tochar()); | |
cs_l.write(1); | |
}; | |
function _spi_read(address) { | |
cs_l.write(0); | |
local rblob = spi.writeread(address.tochar()+"\x00"); | |
cs_l.write(1); | |
//server.log(format("read %02x from %02x",rblob[1], address)); | |
return rblob[1]; | |
}; | |
function _write_arduchip_reg(address, value) { | |
_spi_write((address | 0x80), value); | |
} | |
function _read_arduchip_reg(address) { | |
local value = _spi_read(address & 0x7F); | |
return value; | |
} | |
function flush_fifo() { _write_arduchip_reg(ARDUCHIP_FIFO, FIFO_RDPTR_RST_MASK)} | |
function start_capture() { _write_arduchip_reg(ARDUCHIP_FIFO, FIFO_START_MASK)} | |
function clear_fifo_flag() { _write_arduchip_reg(ARDUCHIP_FIFO, FIFO_CLEAR_MASK)} | |
function read_fifo() { _spi_read(SINGLE_FIFO_READ) } | |
function read_fifo_length() { | |
local len1 = _read_arduchip_reg(FIFO_SIZE1); | |
local len2 = _read_arduchip_reg(FIFO_SIZE2); | |
local len3 = _read_arduchip_reg(FIFO_SIZE3); | |
local length = ((len3 << 16) | (len2 << 8) | len1) & 0x07fffff; | |
return length; | |
} | |
function set_fifo_burst() { | |
spi.write(BURST_FIFO_READ.tochar()); | |
} | |
function get_bit(address, bit) { | |
local tmp = _read_arduchip_reg(address); | |
return (tmp & bit); | |
} | |
function _write_sensor_reg(address, value) { | |
local i2c_err = i2c.write(0x60, address.tochar() + value.tochar()); | |
if (i2c_err) { | |
throw("i2c error:" + i2c_err); | |
} | |
} | |
function _write_sensor_regs(regs) { | |
for(local i = 0; i < regs.len(); i+=2) { | |
_write_sensor_reg(regs[i], regs[i+1]); | |
} | |
} | |
function _read_sensor_reg(reg) { | |
local data = i2c.read(0x61,reg.tochar(),1)[0]; | |
return data; | |
} | |
function reset() { | |
// First, test SPI is working | |
_write_arduchip_reg(0x00, 0xaa); | |
if (_read_arduchip_reg(0x00) != 0xaa) { | |
throw("Error: could not talk SPI to arduchip"); | |
} | |
// Log arduchip version | |
local arduchip = _read_arduchip_reg(0x40); | |
if (arduchip != 0x40 && arduchip != 0x55) { | |
throw(format("Error: did not find expected Arduchip version (0x40/0x55). Version found=0x%02x", arduchip)) | |
} | |
// Read camera ID & check it's valid | |
_write_sensor_reg(0xff, 0x01); | |
local pid = (_read_sensor_reg(0x0a) << 8) | _read_sensor_reg(0x0b); | |
if (pid != 0x2642) { | |
throw(format("Error: did not find expected OV2640 camera (0x2642). PID found=0x%04x", pid)); | |
} | |
// Set system reset bit | |
_write_sensor_reg(0xff, 0x01); | |
_write_sensor_reg(0x12, 0x80); | |
imp.sleep(0.01); | |
// Load default parameter sets | |
_write_sensor_regs(OV2640_JPEG_INIT); | |
_write_sensor_regs(OV2640_YUV422); | |
_write_sensor_regs(OV2640_JPEG); | |
// Set up a default picture size | |
set_jpeg_size(320); | |
} | |
function capture() { | |
flush_fifo(); | |
clear_fifo_flag(); | |
start_capture(); | |
while (!get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK)){}; | |
server.log("Capture Complete"); | |
} | |
function send_chunk() { | |
agent.send("jpeg_chunk", [chunk_next*CHUNK_SIZE, spi.readblob(CHUNK_SIZE)]); | |
// All done? | |
chunk_next++; | |
if (chunk_next < chunk_count) imp.wakeup(0, send_chunk.bindenv(this)); | |
else { | |
cs_l.write(1); | |
agent.send("jpeg_end",1); | |
} | |
} | |
function send_buffer() { | |
local len = read_fifo_length(); | |
chunk_count = math.ceil(len.tofloat()/CHUNK_SIZE).tointeger(); | |
server.log(format("%d bytes: %d chunks",len,chunk_count)); | |
cs_l.write(0); | |
set_fifo_burst(); | |
spi.readblob(1); //dummy read | |
// Send header | |
agent.send("jpeg_start", len); | |
// As buffer can be big, we do the sending on imp.wakeup(0) to allow | |
// incoming messages to be processed | |
chunk_next = 0; | |
imp.wakeup(0, send_chunk.bindenv(this)); | |
} | |
function set_jpeg_size(size) { | |
switch(size) { | |
case 160: _write_sensor_regs(OV2640_160x120_JPEG); break; | |
case 176: _write_sensor_regs(OV2640_176x144_JPEG); break; | |
case 320: _write_sensor_regs(OV2640_320x240_JPEG); break; | |
case 352: _write_sensor_regs(OV2640_352x288_JPEG); break; | |
case 640: _write_sensor_regs(OV2640_640x480_JPEG); break; | |
case 800: _write_sensor_regs(OV2640_800x600_JPEG); break; | |
case 1024: _write_sensor_regs(OV2640_1024x768_JPEG); break; | |
case 1280: _write_sensor_regs(OV2640_1280x960_JPEG); break; | |
case 1600: _write_sensor_regs(OV2640_1600x1200_JPEG); break; | |
default: | |
_write_sensor_regs(OV2640_320x240_JPEG); | |
server.log("Size not recognized, default 320x240 set"); | |
break; | |
} | |
} | |
} | |
// Specified SPI speed is 8MHz | |
// Specified I2C speed is 400kHz | |
const I2C_CLKSPEED = 400000; | |
const SPI_CLKSPEED = 4000; // kHz | |
// Increase outbound packet buffers to make TX faster | |
// wifi only! | |
imp.setsendbuffersize(32768); // returns last | |
server.log("Send Buffers set to "+imp.setsendbuffersize(32768)); | |
// Configure interfaces | |
spi <- hardware.spiPQRS; | |
spi.configure(CLOCK_IDLE_LOW, SPI_CLKSPEED); | |
cs_l <- hardware.pinS; | |
cs_l.configure(DIGITAL_OUT, 1); | |
i2c <- softi2c(hardware.pinXB, hardware.pinXA); | |
i2c.configure(I2C_CLKSPEED); | |
// Sensor I2C | |
i2c_sensor <- softi2c(hardware.pinK, hardware.pinL); | |
i2c_sensor.configure(I2C_CLKSPEED); | |
// Set up camera | |
myCamera <- Camera(spi, cs_l, i2c); | |
myCamera.set_jpeg_size(1600); | |
function capture_loop() { | |
imp.wakeup(60,capture_loop); | |
myCamera.capture(); | |
myCamera.send_buffer(); | |
} | |
capture_loop(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment