- 1 Programming Model 363 Cameras/camera modules
Programming Model 363 Cameras/camera modules
This article reflects the current state of the software to operate Elphel model 363 camera, including synchronizing of multiple cameras. Not all the software is in the final state, but - anyway.
Sensor board control (10347)
CCD sensor front end in the model 363 camera consists of a sandwich of the two PCBs - 10347 - FPGA-based timing/interface module and 10342 - analog sensor front end. In addition to the 10342 board, 10347 controls optional mechanical shutters, it has interface connectors for some 35-mm motorized lenses, opto-isloated I/O for synchronization and general purpose 5VDC output that can be used to turn on fan or drive other circuitry.
This board is controlled from the processor board () through a 2-wire serial interface, there is init347.php (can be reached as http://<camera_ip>:81/init347.php) that sets initial values to the sensor registers (commented in the code), including main and mechanical shutter sequencers. This script is called automatically during camera initialization, it can be used as a sample for custom PHP applications to control the acquisition parameters, for manual changing parameters I would recommend copying it (or modified version) to RAM-disk based TMPFS (ftp to /var/html directory in the camera) - it will be availble as http://<camera_ip>:81/var/init347.php (or you may rename it as you wish, preserving .php extention, of course)
Process of acquiring images from the CCD image sensor is controlled by the sequencer with the program (manually assembled) in the register address space of 0x100..0x1ff (initialized by init347.php) ,the Verilog code of the sequencer that interprets this code is in the seq_cpu.v. Tricky part is that some loop count values (including exposure time) are embedded in the code and the absolute addresses inevitably change when the sequencer code is modified. These addresses are needed for the other scripts to change variable parameters (i.e. exposure), so we use the top sequencer addresses as a table of externally needed addresses, 0x1ff holding program address of exposure count, 0x1fe - delay between the start of additional mechanical shutter sequencer and the start of exposure. More entries may be added in the future (i.e. delay between exposure and readout start to give time for mechanical shutter to close and overall trigger delay)
Current version of init347.php looks for the mechanical shutter sequencer program (0x200-0x27f for odd/all exposures, 0x280-0x2ff - for even ones) and if it is empty - programs default sequence (will not change existent sequence). Mechanical shutter sequencer operates independently of the main one, it only starts at the moment defined by the main sequencer. There is a separate program mshutter.php that allows to edit the sequence(s) in a human-readable/writable form - the init347.php contains just codes for the shutter_seq.v - each word include the 8-bit output code for the shutter control board (10368) and duration that code should stay on the output.
Programming the CPU board 10353 to receive/compress images
This part of the code is not redesigned yet to support CCD imagers, it was intended for the CMOS sensors originally. So currently you may just use the ccam.cgi interface to acquire an image (the frame dimensions should match those specified for the sensor in init347.php or equivalents), some parameters currently have no effect (like "e=" - exposure time) - it will be fixed in the future, others are needed (JPEG compression quality, image size, bit width, color type (monochrome/color/jp4), gamma, color saturation. The following url should open the image and initialize 10353 board FPGA to acquire/compress images and place them into circbuf:
Acquiring images and transferring them to the host
After the FPGA is programmed to process the incoming sensor data you do not need to use ccam.cgi - only if any of the parameters above need to be changed, Later we'll develop new software to control acquisition process - most of the functionality can already be implemented in PHP, but several IOCTL-s (not PHP-friendly) are still required. Now the process of transferring images is separate from the image acquisition to the camera memory (a 19MB circular buffer circbuf). The acquired data can be asynchronously sent out as either individual JPEG images through imgsrv server or as RTP/RTSP MJPEG-ecoded stream (not sure if it still works with that large images, maybe a few fixes are needed). Imgsrv server allows images/image metadata transferring, waiting for a new image to appear in the buffer, manipulating the frame read pointer. A sequence of commands can be sent in a single http request through url line (commands are separated by slashes, a list of commands is available if you just open the imgsrv URL http://<camera_ip>:8081 without parameters.
Image acquisition to the camera memory is controlled separately and consists of two parts controlled by two small PHP scripts:
compressor.php - FPGA image acquisition and compression
This script is sensor-independent and is used as a front-end to the renewed interface to the JPEG_CMD_??? commands (defined in os/linux-2.6/include/asm-cris/elphel/c313a.h. Of those commands only 3 (or even 2) are needed for the implementation of the described functionality and these commands can be entered as mnemonics (run/stop/reset), but the script other ones as numeric values from 1 through 16. http://<camera_ip>:81/compressor.php will provide command list, http://<camera_ip>:81/compressor.php?cmd=run will turn the acquisition/compression modules in the 10353 on-board FPGA into constant processing mode - FPGA will acquire all the incoming (from the sensor board port) images, compress them and store in the circbuf. It will also generate interrupts at the end of each frame compression (it lags only by 20 lines plus 1000 or so pixels behind the image data from the sensor). Interrupt service routine updates frame write pointer, frame number, frame metadata and finishes waits for the nest frame (i.e."wait' command passed to imgsrv server on the port 8081).
When FPGA is programmed to the constant compression mode, the next thing is to trigger the sensor board - once per each frame to be acquired.
trig.php - sensor board triggering
Currently trig.php is sensor board specific and works only with the 10347 board. It provides the following functions:
- software triggering of the image acquisition
- control of triggering from external source through the opto-isolated input on the 10347 board and
- generating output signal (on the "fan" connector) that can be used to trigger other (and this too) cameras.
So here are the commands:
- http://<camera_ip>:81/trig.php?cmd=immed - software triggering of the camera (there is a >1 second delay for the first image after long timeout - the timeout time is controlled by init347.php, it can be set to "forever on".
- http://<camera_ip>:81/trig.php?cmd=arm - Prepare camera to be triggered through an external input. One such command is enough for any number of trigger pulses - each will trigger a new image acquisition (if they are far enough to allow for the whole frame to be transferred)
- http://<camera_ip>:81/trig.php?cmd=disarm - after this command camera will ignore incoming signals on external trigger input
- http://<camera_ip>:81/trig.php?cmd=single - arm camera for one external trigger only. It will auto disarm after processing a single input trigger pulse
- http://<camera_ip>:81/trig.php?cmd=fireall - generate external trigger out pulse on a "fan" output. If several cameras have their trigger inputs connected to this output (pins 2 connected directly, pin 1 on has inline 360 Ohm resistor at each input branch) _AND_ each of them is armed _AND_ each of them is programmed (ccam.cgi) _AND_ each of them is in constant compression mode (compressor.php) then that command is all what is needed to trigger all the cameras in parallel (required individual delays can be implemented in teh sequencer code).
Combining everything together
Here is a sample algorithm to run simultaneous acquisition on several camera modules, one of them being the master one (the one that has its fan output connected to all trigger inputs (including its own).
- Modify shutter sequence with http://<camera_ip>:81/mshutter.php, if needed. Updated sequence can be embedded in the init347.php that is automatically executed each time camera starts up
- Update camera settings with custom script, based on init347.php. Do not use constants for the main sequencer addresses as they may change at any time - read table at the end of sequencer program memory
- Acquire one image from each camera, discard result:
- wget "http://<camera1_ip>:81/admin-bin/ccam.cgi?opt=*vhc&ww=4008&wh=2676&bit=8&iq=95&byr=0&gam=99&rscale=1&bscale=1&e=1" -O /dev/null,
- wget "http://<camera2_ip>:81/admin-bin/ccam.cgi?opt=*vhc&ww=4008&wh=2676&bit=8&iq=95&byr=0&gam=99&rscale=1&bscale=1&e=1" -O /dev/null
- wget "http://<cameraN_ip>:81/admin-bin/ccam.cgi?opt=*vhc&ww=4008&wh=2676&bit=8&iq=95&byr=0&gam=99&rscale=1&bscale=1&e=1" -O /dev/null
- Put each camera in constant acquisition mode
- wget "http://<camera1_ip>:81/compressor.php?cmd=run" -O /dev/null
- wget "http://<camera2_ip>:81/compressor.php?cmd=run" -O /dev/null
- wget "http://<cameraN_ip>:81/compressor.php?cmd=run" -O /dev/null
- Enable external triggering of the cameras
- wget "http://<camera1_ip>:81/trig.php?cmd=arm" -O /dev/null</nowiki>
- wget "http://<camera2_ip>:81/trig.php?cmd=arm" -O /dev/null</nowiki>
- wget "http://<cameraN_ip>:81/trig.php?cmd=arm" -O /dev/null</nowiki>
- Start image acquisition processes on the host to repetitively read images, one per each camera. It can be something like
- wget "http://<camera1_ip>:8081/torp/wait/img/next/save" -O camera1_$(NUM).jp4
- wget "http://<camera2_ip>:8081/torp/wait/img/next/save" -O camera2_$(NUM).jp4
- wget "http://<cameraN_ip>:8081/torp/wait/img/next/save" -O cameraN_$(NUM).jp4
- Loop: Start a process on each camera to detect end of image acquisition. Optionally it is possible to watch just one camera if the settings are the same (possibly adding some uncertainty time that is one CCD cleaning cycle period of the sequencer):
- wget "http://<camera1_ip>:8081/towp/wait/" -O /dev/null
- wget "http://<camera2_ip>:8081/towp/wait/" -O /dev/null
- wget "http://<cameraN_ip>:8081/towp/wait/" -O /dev/null
- Trigger all the cameras at once (assuming camera1 is a master)
- wget "http://<camera1_ip>:81/trig.php?cmd=fireall" -O /dev/null</nowiki>
- Wait for all processes started in the step 7 to terminate - that means that the current frames compression is over, cameras are ready for the new frame
- End of loop - go to step 7 until some acquisition parameters are needed to be changed. Then proceed:
- end constant compression with
- wget "http://<camera1_ip>:81/compressor.php?cmd=stop" -O /dev/null</nowiki>
- wget "http://<camera2_ip>:81/compressor.php?cmd=stop" -O /dev/null</nowiki>
- wget "http://<cameraN_ip>:81/compressor.php?cmd=stop" -O /dev/null</nowiki>
- go to step 2.
The sequence above assumes that images are read to the host (on average) faster than they are acquired. If that is not the case additional loop is needed to prevent buffer overrun (it is 19MB currently) - count number of images acquired and images transferred and wait with thriggering if the difference grows to high. It is also possible to read xml files from http://<camera_ip>:8081/pointers - it will (among other data) return number of unread images in the buffer.
Each image read out contains Exif data with the FPGA timestamp of the exposure. THe FPGA clocks can be set by writing to the 10347 registers \using init347.php - based program