czwartek, 14 marca 2013

IP-Cam / Cool Cam (Foscam clone) hacking - custom firmware (draft version)

 

 

The foscam-like pan/tilt camera is a nice little gadet, for little money one can get an interesting linux-based embedded platform.

Hardware features:


- ARM7TDMI CPU clocked at 80 MHz
- 16 MB RAM
- 4 MB Flash
- Ralink 3072 WiFi module (rt2x00 compatible)
- 10/100 MBps Ethernet
- Two unipolar stepper motors (Horizontal/Vertical)
- PixelArt USB camera (640x480)
- 10 IR LEDs used for (a kind of) night vision
- Realtek audio chip


There are already a lot of sites covering the same subject so there's no point in reinventing the wheel. What makes this project different is the attempt to implement:

  • Stepper motors control
  • MMC/SD card over software-emulated SPI bus

Limitations:


  • No MMU 
The CPU ARM7TDMI doesn't have a MMU unit (memory management unit) - so the normal/real Linux operating system can't be used, the only solution is "uClinux" - which has some further implications like for instance: no virtual memory, all processes share the same linear address space, no memory protection, various issues with mmap(), no fork() function etc.

  • It's slow...
The rough estimate is only about ~39 BogoMIPS. In other words: no mp3 playback, motion detection will have to be clever and simple.


Recommended reading:


There's a very useful forum dedicated to foscam-like devices, I strongly recommend reading it, especially the below thread:

Tool-chain & Kernel 3.x  (even though I'm using 2.6.x - but the basic rules are the same)

http://www.openipcam.com/forum/index.php/topic,204.0.html

Also, in order to change the firmware to a custom one a soldering iron is needed, the procedure is described here:

http://www.gadgetvictims.com/2009/12/bring-your-fi8908w-paperweight-back-to.html


Base Kernel:

This firmware project is based on Linux Kernel version 2.6.38, ported to ARM7 by Wan ZongShun, who kindly made ARM-specific files available here:

http://nuc700.git.sourceforge.net/git/gitweb.cgi?p=nuc700/nuc700;a=summary

Some hacks and ideas were also "borrowed" from the openipcam.com forum user "robert-qfh", his git repository:

https://github.com/rhuitl/uClinux

In addition to the above, some additional changes were required to support the platform hardware features, below you can find the result of my efforts.

flat.h - patch to get rid of nasty/random "bad relloc" errors in userspace apps


.../uClinux-dist/linux-2.6.x/arch/arm/include/asm/flat.h

change line 10 to:

#define flat_reloc_valid(reloc, size)           ((reloc) <= (size)+0x1000)

explanation:

http://linux-kernel.2935.n7.nabble.com/PATCH-Valid-relocation-symbol-for-FLAT-format-on-ARM-td398928.html

WiFi driver rt2x00 - apparently the hardware encryption is broken and should be disabled

 

The internal USB-WiFi module identifies itself as a Ralink 3070 802.11n device, dmesg output:

usb 1-2: New USB device found, idVendor=148f, idProduct=3070
usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-2: Product: 802.11 n WLAN
usb 1-2: Manufacturer: Ralink
usb 1-2: SerialNumber: 1.0

Normally, it should work well with the default rt2x00 drivers bundled with the 2.6.38 kernel wireless section, but as it usually happens, some additional tweaks are needed to make it stable.

Disable power saving:

.../linux-2.6.x/drivers/net/wireless/rt2x00/rt2800lib.c
   
/*
     * Disable powersaving as default on PCI devices.
     */
    //if (rt2x00_is_pci(rt2x00dev) || rt2x00_is_soc(rt2x00dev))
        rt2x00dev->hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;

    /*

By default powersaving is disabled only for PCI devices, comment out the above "if" statement to disable it completely

Disable hardware encryption:

With hardware encryption enabled (default state) the wifi can be successfully initialised, associated with an AP, but it's unstable - missing ARPs, slow pings etc.

To disable hw_crypt: edit rt2800usb.c file:

set the following parameter:

static int modparam_nohwcrypt = 1;


and/or (to be really sure):

comment out that statement:

    if (!modparam_nohwcrypt)
        __set_bit(CONFIG_SUPPORT_HW_CRYPTO, &rt2x00dev->flags);

In addition, you may also try playing with the TX queue size, it affects both performance and stability (google for the infamous "missed TX status report").

static const struct data_queue_desc rt2800usb_queue_tx = {
    .entry_num        = 128,
    .data_size        = AGGREGATION_SIZE,
    .desc_size        = TXINFO_DESC_SIZE + TXWI_DESC_SIZE,
    .priv_size        = sizeof(struct queue_entry_priv_usb),
};

By default the .entry_num is 128 which IMHO is bit too high for a slow system - it seems there is much less unnecessary overhead when max queue length is reduced to 8/16.

Also, one thing worth mentioning with regards to disabled hw encryption. Basically it means that all encryption must be handled be the main CPU which has a big negative impact on the overall system performance when WPA/WPA2 method is used. For this reason the CCMP encryption should not be used (it would eat all spare CPU cycles, instead use the TKIP both for group and pairwise packets. It can be enforced in the wpa_supplicant.conf file:

network={
ssid="your_sid"
scan_ssid=1
group=TKIP
pairwise=TKIP
proto=WPA
key_mgmt=WPA-PSK
psk="secret_pass"
}



Stepper motors control from within userspace application

 

Stepper motors are connected to the CPU GPIO space via two additional ICs:

- ULN2803 (8 x Darlington transistor array) it's used to switch on/off an individual coil, each motor has 4 coils.

- 74HCT259 - Configured to act as an addressable latch, three address lines A0, A1, A2 are used to select the desired output logical state (low/high). Apparently, the LE (latch enable) pin is hardwired to GND which is far from being perfect as it may introduce some transient "glitches" when address lines are changed.

CPU -> 74HCT259 mapping:

GPIO PORT5 output register (0xFFF83058):

bit2 = A0
bit3 = A1
bit4 = A2
bit5 = D (data pin)


I'm using the following code, it's just for testing purposes. In long term it should be cleaned and rewritten as a kernel module.


#define inpw(port) (*((volatile unsigned int *)port))
#define outpw(port,val) (*((volatile unsigned int *)port)=(val))


//set stepper & led GPIO cfg
void cfgGPIO()
{
outpw(0xFFF83050, inpw(0xFFF83050) & ~0xFF0); //PORT5 GPIO: 2,3,4,5
outpw(0xFFF83054, inpw(0xFFF83054) | 0x3C); //PORT5 2,3,4,5 output

outpw(0xFFF83040,  inpw(0xFFF83040) & ~((1<<20) + (1<<21)) ); //  led port4 gpio
outpw(0xFFF83044,  inpw(0xFFF83044) | 0x400); //PORT4 output 4
}


// count - number of steps
// h_or_v - select vertical or horizontal motor
// dir - select direction, 0 - normal, 1 - reverse (left/right, up/down)
int step(int count,int h_or_v, int dir)
{
unsigned long port = 0xFFF83058, ledport=0xFFF83048;

unsigned long tab1[] = {0,4,8,12,16,20,24,28};

unsigned long tabV[] = {0,4,8,12};
unsigned long tabH[] = {16,20,24,28};
unsigned long *tabptr;

int l=0,k;
unsigned int tempVal;

        if (h_or_v == 0) tabptr = tabH ; //select horizontal motor
        else tabptr = tabV;//vertical motor

//clear all outputs

        for(l=0;l<8;l++)
        {
                 tempVal = (inpw(port) & ~60) | tab1[l];
                 outpw(port, tempVal); //only address bits are set here, D always low
        }


//cycle

for(k=0;k<count;k++)
{
        for(l=0;l<4;l++)
        {
                //outpw  don't touch other bits (they might be used by the SPI/MMC hack - both are using PORT5)
                // get PORT5 val; clear bits 2,3,4,5 - "and NOT 60";  set the latch address according to tab pre-defined values using "OR"


                if(dir==0) tempVal = ( inpw(port) & ~60) | tabptr[l];
                else tempVal = ( inpw(port) & ~60) | tabptr[3-l]; //reverse gear

                     outpw(port, tempVal | 32); // 32 = PORT5.5 = 74HCT249 D pin
                        outpw(ledport, 0x0); //led on - REVISE !
                        usleep(10000); //10 ms - REVISE !
                outpw(port, tempVal ); //D pin LOW;
                outpw(ledport, 0x400); //led off - REVISE !
                usleep(5000); //5 ms - REVISE !
        }
}
//clear all outputs, very important - don't skip this step !!! (coils might overheat)
        for(l=0;l<8;l++)
        {
                tempVal = (inpw(port) & ~60) | tab1[l];
                outpw(port, tempVal); //only address bits are set here, D always low
        }
return(0);
}

As indicated in the code above, it's crucially important to always switch off all coils ! If from whatever reason this step is omitted the affected stepper motor will become very hot and eventually the internal coil(s) might be damaged (then  the current flow can be increased even more - leading to a potentially hazardous scenario !)



SD/MMC storage


It's possible to implement SD/MMC card storage by emulating software SPI bus connected to GPIO pins. The easiest way is to use pins originally connected to horizontal / vertical edge position sensors, no need to mess with tiny smd soldering points - just cut & connect.



more details soon...

SPI-over-GPIO - the out-of-the-box version works but is unacceptably slow (<30 KB/s raw)


An optimised solution was needed (direct I/O pin access, bypassing linux general gpio locking)

more details comming soon...

MMC/SD block driver - by default crashes the filesystem if  even a single write error should occur


Retry routine added to the default MMC block driver

more details comming soon...

 

Camera sensor access

 

The default V4L driver in 2.6.x no longer supports memory mapping operations with no-mmu CPU, fortunately there is a patch to bring back mmap capabilities.

more details soon...