Introduction

The HP4470C is a USB/Parallel flatbed scanner. This scanner is based on the RealTek RTS8891 scanner chip. I haven't been able to obtain the data sheets for this chip so far so the following document outlines observations made about the operations of the device by looking at the USB dumps produced by USBSnoop as well as reverse engineering (disassembly) of the windows drivers supplied by HP.
Thanks for the first general information's from Michael Cohen . But the first source don't work with my scanner, the lamp doesn't switch on. My first investigation shows that HP using different boards in the scanner. The Windows driver reads a MainboardID and use it for different values by init the scanner. I have now updated the sources with the init and the calibration sequence from my scanner.
I'm using for disassemble the Windows DLL's hpg4400.dll and rts8891u.dll. The HP-setup use the same DLL's for this two scanners. The Windows driver (rts8891u.dll) reads a MainboardID and use it for different values by init the scanner.
All investigation's based at the moment on USB. I think, after understand the chipset, it is easy to implement the parallel interface.


Hardware

Here the general HW, what i found (bold = my scanner):

Model

Vendor

ID

Mainboard_ID

ASIC /IC's

Analog front-end

hp4400c

03F0

0705

?

ASIC 13010070-5011 (RealTek RTS8891)

built-in

hp4470c

03F0

0805

0x00

ASIC 13010070-5011 (RealTek RTS8891)
Crystal 12.000 MHz
512K DRAM C41C16256-35K
STEPPER MOTOR DRIVER L6219DS
EEPROM HT93LC46-A
Perpheral driver (0.5A)ULN2003AN

built-in
I found only a little pcb without a ASIC or IC. The ? is labeled as
WBCBQ1C025077K
94000099-1000


General USB characteristics

The 4470c has one configuration (1) with a single interface (0), the interface in turn has 3 end points:

ep

Type

Direction

0x81

Bulk

IN

0x02

Bulk

Out

0x83

Interrupt


This scanner does not use control pipes at all - all communications to the device occur using bulk mode. The device has 244 registers. These can be accesses one at the time or a whole sequence of registers may be accessed simultaneously.

The general command sent over the bus has the following format:

Byte offset

Number of bytes

Meaning

00

01

Command

01

01

Register

02

02

Count

03

04-

Variable

Optional Data

The command ID and register are each 1 byte. The count is two bytes and is given with the most significant byte first. For write commands with a count of 0, or for any read command, the size of the command block is 4 bytes.


Commands

The following commands have been observed:

Command ID

Read/Write

Purpose

Description

0x80

Read

Read register

This command is issued with an OUT transfer on ep 0x81. The count specifies how may bytes are required to be returned from the device. In order to actually read those the host needs to initiate a transfer for the required number of bytes for the IN direction:

example:
The following reads one register (25) to test the state of the buttons on the front of the device:

> transfer type=bulk size=4 ep=0x02 dir=OUT
> 80 25 00 01
> transfer type=bulk size=1 ep=0x81 dir=IN
< 00

0x81

Read

Read some calibration data


0x88

Write

Set register

This command sets the register or group of registers specified. There is no need to read any data after issuing this command so only one transfer is required. It seems that the maximum packet size is 0xb4 registers to be set at once. So if you need to write all 0xf4 registers its necessary to split into two packets.

0x89

Write

Write data to calibration

This command is used to write 2072 (0x818) bytes of a sequentially increasing pattern to the scanner. used for calibration.

0x8A

Write

Unknown


0x90

Read

Read qty of bytes in the download buffer

(read 3 bytes. eg. 0x90 0x00 0x00 0x03) this command queries the buffer as to how many bytes are present for download during getting at image. Note that the maximum USB transfer size per request appears to be 0xffc0.

0x91

Read

Read download buffer

Read this many bytes from the scanner buffers. Maximum 0xffc0.

example:

The following resets the button state after reading it (above).
> transfer type=bulk size=5 ep=0x02 dir=OUT
> 88 25 00 01 00

Note that it seems to be possible to combine packets. Hence if we want to set
a variable and immediately read another:

> transfer type=bulk size=9 ep=0x02 dir=OUT
> 88 da 00 01 a0 80 10 00 02
> transfer type=bulk size=2 ep=0x81 dir=IN
< 68 1a


Registers:

The following registers have been identified:


Mainboard ID

0

0

0

0

0

0

REG adress

description

Write REG set myinit32,

init for scan 300dpi


Write REG set myinit33,

scan

grayscale 300dpi


Write REG set pv300dpi_24








Write REG set pv300dpi_20








Write REG set pv300dpi_21








_300_true_2_30










0x00

1 Byte WRITE: This register is normally set to 0xf5, but before reading any actual image data it needs to be set to 0xe5 otherwise all data will be 0x90 (maybe its enabling the CCD?)

0xE5

0xE5

0xE5

0xE5

0xE5

0xE5

0x01

1 Byte WRITE: every time 0x41

0x41

0x41

0x41

0x41

0x41

0x41

0x02..03

1 Word WRITE:

0x1F1F

0x7F7F

0xFAFA

0x8682

0x8682

0x8682

0x04..05

1 Word WRITE:

0x1F1F

0x7F7F

0xFAFA

0x8686

0x8686

0x8686

0x06..07

1 Word WRITE:

0x1F1F

0x7F7F

0xFAFA

0x8286

0x8286

0x8286

0x08

1 Byte WRITE: Brightness for eatch color? Normaly 0x10. A higher value gives more brightness. (0..0x20)

0x0A

0x10

0x00

0x06

0x06

0x06

0x09

1 Byte WRITE: Brightness for eatch color? Normaly 0x10. A higher value gives more brightness. (0..0x20)

0x0A

0x10

0x00

0x05

0x05

0x05

0x0A

1 Byte WRITE: Brightness for eatch color? Normaly 0x10. A higher value gives more brightness. (0..0x20)

0x0A

0x10

0x00

0x0A

0x0A

0x0A

0x0B

1 Byte WRITE: every time 0x70

0x70

0x70

0x70

0x70

0x70

0x70

0x0C..0x0F

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x10,0x11

2 Byte WRITE: Controls little green LED's for color selector

0x10: (0x20 - color) (0x10- B/W) bit 7 controls how many bits per color to produce. e.g. 0x20 1 bit per pixel, 0x28 2 bits per pixel?

0x11:

  • Bit 3 (r) is current movement status - set stopped, cleared moving

  • Bit 5 (w) Controls which light source to use - bit 5 (e.g. 0x1b) on - slide light (XPA), off (e.g. 0x3b) - bed light

  • Bit 6 (r) direction of movement: set forward, reset reverse.

Values : 28, 3b, 3f

0x283F

0x283F

0x203B

0x203B

0x203F

0x203B

0x12

1 Byte WRITE: every time 0xFF

0xFF

0xFF

0xFF

0xFF

0xFF

0xFF

0x13

1 Byte WRITE: bit 1 if on- the resulting scan has a zero for each other pixel, every time 0x20

0x20

0x20

0x20

0x20

0x20

0x20

0x14..0x15

2 bytes WRITE, every time 0xF8 ,0x28

0xF828

0xF828

0xF828

0xF828

0xF828

0xF828

0x16

1 Byte WRITE:

0x07

0x07

0x07

0x07

0x07

0x01

0x17

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x18

1 Byte WRITE: every time 0xFF

0xFF

0xFF

0xFF

0xFF

0xFF

0xFF

0x19

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x1A

1 Byte READ: Buttons: Some more button registers like 0x25:

0x00

0x00

0x00

0x00

0x00

0x00

button icon

mask


Red triangle

0x01

spanner

0x02

color

0x04


0x1B..0x1C

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x1D

1 Byte READ: Micro switch
for scanner movement. When rewinding the software constantly checks for bit 2. If not touching reg is 0x20, when touching reg is 0x22. Bit 1 = on, allow to write reg 8A.

1 Byte WRITE: every time 0x20

0x20

0x20

0x20

0x20

0x20

0x20

0x1E..0x1F

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x20-0x22

3 Byte WRITE : LCD-Display
Three bytes controlling frontpanel LCD display(see below).

0x3AF200

0x3AF200

0x3AF200

0x3AF200

0x3AF200

0x3AF200

0x23

1 Byte WRITE:

0xFF

0xFF

0xFF

0xFF

0xFF

0x80

0x24

1 Byte WRITE:

0xFF

0xFF

0xFF

0xFF

0xFF

0xFF

0x25

1 Byte Read: Buttons
Returns the state of the front button panel. Each bit represents a button. If a button has been pressed, that bit is set. Note that the register will hold its value until it has been explicitly cleared by being written to. This register only works if 0xb3 has the second bit set (0x04).

Button-icon mask

power 0x80

scanner 0x40

www 0x20

copy 0x10

email 0x08

print 0x04

- 0x02

+ 0x01

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x26..0x31

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x32..0x33

Movement two Byte. Values 0x8081(myinit33), 0x2083 (pv600dpi_11), 0x0003(pv300dpi_02..12), 0x0000(myinit30)

0x33:
Bit 8 controls weather to move or not 0x80 moves 0x00 does not. The lower nibble represents the speed - 0x01 is fastest. step size=16/nibble. Note that the number of steps to move is in 0-0x64. The actual distance depends on this step size. 0X8082 makes the scaner slower.

0x0000

0x8081

0x0003

0x2083

0x2083

0x2083

0x34

1 Byte WRITE: every time 0x10

0x10

0x10

0x10

0x10

0x10

0x10

0x35

1 Byte WRITE: values 0x00(pvinit10), 0x0e(pvinit11), 0x1b(myinit33), 0x45(pvinit13), 0x47 (pv600dpi_12)

0x00

0x1B (scan)

0x0E (move)

0x47

0x47

0x47

0x36

1 Byte WRITE: Movement, controls the direction of movement 0x24 reverse, 0x2c forward. Note that when rewinding the values in 0x60,0x62 are ignored, the scanner always goes back to the home position and stops. Note that there are a number of step sizes: 0x2c is half the step size of 0x29.

0000 D000, D = direction.

0x07

0x29

0x22

0x2C

0x2C

0x2C

0x37, 0x38

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x39

1 Byte WRITE: after init (0x00) everytime 0x02

0x00

0x02

0x02

0x02

0x02

0x02

0x3A

1 Byte WRITE: Movement, values 0x00(pvinit10), 0x0e(pvinit12),0x1b(myinit33), 0x43(pvinit13)
Scan or quick move- 1b scans and 0e moves quickly(in the most cases!?).

0x00

0x1B

0x0E

0x43

0x43

0x43

0x3B..0x3E

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x40

1 Byte WRITE: values 0x20(normaly), 0x2c(pv300dpi_20 / 21, pv600dpi_11 / 12)

0x20

0x20

0x20

0x2C

0x2C

0x2C

0x41..0x43

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x44

1 Byte WRITE:

every time 0x8c

0x8C

0x8C

0x8C

0x8C

0x8C

0x8C

0x45

1 Byte WRITE: every time 0x76

0x76

0x76

0x76

0x76

0x76

0x76

0x46..0x5F

1 Byte WRITE: every time 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x60, 0x62

2 Byte WRITE : (each LSB first):
target Movement position. This set of registers holds the required number of steps to move. For simple movements (like to reposition the scanner) 0x60 should be 1 less than 0x62

xxxx

xxxx

xxxx

xxxx

xxxx

xxxx

0x64

1 Byte WRITE : after init (0x00) everytime 0x01

0x00

0x01

0x01

0x01

0x01

0x01

0x65

1 Byte WRITE : Scan control, must be 0x20 to enable for scanning, 0x00 for just moving.

0x00

0x20

0x20

0x20

0x20

0x20

0x66

2 bytes: WRITE : Scan from this horizontal pixel(high byte, low byte). Only in normal mode?

0x0000

xxxx

xxxx

0010

0010

0010

0x68..0x6B

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x6C..0x6D

2 bytes: WRITE : Scan to this horizontal pixel. Only in normal mode?

0x0000

xxxx

xxxx

xxxx

xxx (028E)

xxx (028E)

0x6E..0x71

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x72

1 Byte WRITE :

0xE1

0xE1

0xE1

0xE1

0xE1

0xE1

0x73

1 Byte WRITE :

0x14

0x14

0x14

0x14

0x14

0x14

0x74

1 Byte WRITE :

0x18

0x18

0x18

0x18

0x18

0x18

0x75

1 Byte WRITE :

0x15

0x15

0x15

0x14

0x15

0x15

0x76..0x78

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x79..0x7A

2 bytes, WRITE : values 0x0000(myinit32), 0x2001(myinit33)

0x0000

0x0000

0x2001

0x2001

0x2001

0x2001

0x7B..0x7F

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x80..0x83

4 Byte WRITE : two words values 0x0000 0x0000(myinit32), 0x3200 0x3300(myinit33), 0xaf00 0xb000(pv300dpi_20/21), 0xb000 0xb100(pv300dpi_22/23)

0x0000

0x0000

0x3200

0x3300

0xB000

0xB100

0xAF00

0xB000

0xAF00

0xB000

0xAF00

0xB000

0x84

1 Byte READ : The software reads 0xb (11) bytes from this register on. Disassembly shows that only the last byte is used with 0x60 mask. If not set the lamp needs warming, If set, lamp is ready.

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x85

1 Byte WRITE, values 0x00(myinit32), 0x06(myinit33), 0x0b(pv300dpi_20/21)

0x00

0x00

0x46

0x46

0x46

0x46

0x86

1 Byte WRITE, values 0x00(myinit32), 0x06(myinit33), 0x0b(pv300dpi_20/21)

0x00

0x06

0x0B

0x0B

0x0B

0x0B

0x87

1 Byte WRITE, values 0x00(myinit32/33), 0x8c(pv300dpi_20/21)

0x00

0x00

0x8C

0x8C

0x8C

0x8C

0x88

1 Byte WRITE, values 0x00(myinit32), 0x06(myinit33), 0x10(pv300dpi_20/21)

0x00

0x06

0x10

0x10

0x10

0x10

0x89

1 Byte WRITE, values 0x00(myinit32), 0x34(myinit33), 0xb1(pv300dpi_20/21), 0xb2(pv300dpi_24)

0x00

0x34

0xB2

0xB1

0xB1

0xB1

0x8A

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x8B

1 Byte WRITE : everytime 0xff

0xFF

0xFF

0xFF

0xFF

0xFF

0xFF

0x8C

1 Byte WRITE : everytime 0x3f

0x3F

0x3F

0x3F

0x3F

0x3F

0x3F

0x8D..0x8E

1 Word WRITE, values 0x8068(myinit32), 0x3b60(pv300dpi_20)

0x8086

0x8086

0x3B60

0x3B60

0x3B60

0x3B60

0x8F

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0x90

1 Byte WRITE, values 0x00(myinit32), 0x1c(myinit33), 0x18(pv300dpi_21,pv600dpi_11/12)

0x00

0x1C

0x1C

0x18

0x18

0x18

0x91..0x92

2 bytes WRITE: Horizontal calibration goes here. WRITE_REGS : everytime 0x00

0x0000

0x0000

0x0000

0x0000

x00000

x00000

0x93

Vertical calibration goes here (number of lines to shift each color by)

0x01

0x01

0x01

0x01

0x01

0x01

0x94

1 Byte WRITE: everytime 0x0e

0x0E

0x0E

0x0E

0x0E

0x0E

0x0E

0x95..0xA2

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xA3

1 Byte WRITE: Controls the clock? everytime 0xcc

0xCC

0xCC

0xCC

0xCC

0xCC

0xCC

0xA4

1 Byte WRITE: everytime 0x27

0x27

0x27

0x27

0x27

0x27

0x27

0xA5

1 Byte WRITE: everytime 0x64

0x64

0x64

0x64

0x64

0x64

0x64

0xA6..0xAF

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xB0

1 Byte READ: this register is called from the function RT_CheckScannerLinked. It seems to always return 0x80. Test for b3 to go from 08 to 00 when the scanner finished moving and is ready.

WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xB1

1 Byte WRITE: Mainboard ID (0..3). The windows driver read this register and use it to modify the values and is setting the CCDType, (1,2,3,4,6). WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xB2

1 Byte WRITE: used for moving: values 0x02(myinit32), 0x06(pv600dpi_02),switch on/off the green power LED

0x02

0x02

0x02

0x02

0x02

0x02

0xB3

1 Byte WRITE: controls movement. The software seems to issue the sequence 02 00 08 to this register to start with movement. It then checks 08 to see if still moving. When the scanner stops moving it becomes 0. It is also possible to set it to 0 to stop the scanner. The register also need to be set to 0x04 before the button can be read.

0x00

0x00

0x00

0x00

0x00

0x00

0xB4..0xBD

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xBE

1 Byte WRITE: values 0xff(myinit32), 0x06(myinit34,pv300dpi_20)

0xFF

0xFF

0xFF

0x06

0x06

0x06

0xBF

1 Byte WRITE: values 0x0f(myinit32), 0xe6(myinit34,pv300dpi_20)

0x0F

0x0F

0x0F

0xE6

0xE6

0xE6

0xC0

1 Byte WRITE: values 0x00(myinit32), 0x67(myinit34,pv300dpi_20)

0x00

0x00

0x00

0x67

0x67

0x67

0xC1..0xC8

1 Byte WRITE :

0xFF

0xFF

0xFF

0xFF

0xFF

0xFF

0xC9

1 Byte WRITE: values 0x00(myinit32), 0x07(myinit34,pv300dpi_20)

0x00

0x00

0x00

0x07

0x07

0x07

0xCA

1 Byte WRITE: values 0x0e(myinit32), 0x00(pv300dpi_20/21), 0x0e(pv300dpi_22)

0x0E

0x0E

0x0E

0x00

0x00

0x00

0xCB

1 Byte WRITE

0x00

0x00

0x00

0xFE

0xFE

0xFE

0xCC

1 Byte WRITE

0x00

0x00

0x00

0xF9

0xF9

0xF9

0xCD

1 Byte WRITE

0xF0

0xF0

0xF0

0x19

0x19

0x19

0xCE

1 Byte WRITE

0xFF

0xFF

0xFF

0x98

0x98

0x98

0xCF

1 Byte WRITE

0xF5

0xF5

0xF5

0xE8

0xE8

0xE8

0xD0..0xD1

2 Byte WRITE: Signed chars black level and white level. These control the intensities at which (FIX ME)?

0xF7EA

0xF7EA

0xF7EA

0xEAF3

0xEAF3

0xEAF3

0xD2

1 Byte WRITE

0x0B

0x0B

0x0B

0x14

0x14

0x14

0xD3

1 Byte WRITE

0x03

0x03

0x17

0x02

0x02

0x02

0xD4

1 Byte WRITE

0x05

0x05

0x01

0x04

0x04

0x04

0xD5

1 Byte WRITE: everytime 0x86

0x86

0x86

0x86

0x86

0x86

0x86

0xD6

1 Byte WRITE

0x1B

0xAB

0x0F

0x0F

0x0F

0x0F

0xD7

1 Byte WRITE: everytime 0x30

0x30

0x30

0x30

0x30

0x30

0x30

0xD8

1 Byte WRITE

0xF6

0xF6

0x52

0x52

0x52

0x52

0xD9

1 Byte WRITE: Turns on the lamp, with its MSB (Set to 0x80 for lamp on 0x00 for off).everytime 0xAD

0xAD

0xAD

0xAD

0xAD

0xAD

0xAD

0xDA

1 Byte WRITE: Controls lamp brightness. Values (0xa0, 0xa7).
0xaf is weakest and 0xa0 is brightest. If the low nibble of 0xda is reset, than 0x11 resets its 3rd bit. Defaults to 15.

0xA7

0xA7

0xA7

0xA7

0xA7

0xA7

0xDB..0xE1

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xE2

1 Byte WRITE: Step Size
This is multiplied by register 0x60 to work out how far to move.

0x01

0x01

0x07

0x0F

0x0F

0x0F

0xE3

1 Byte WRITE: values 0x00, 0x85(pv300dpi_20), 0x0d

0x00

0x00

0x00

0x85

0x85

0x85

0xE4

1 Byte WRITE: values 0x00, 0x03(pv300dpi_20), 0x06

0x00

0x00

0x00

0x03

0x03

0x03

0xE5

1 Byte WRITE

0x14

0x1C

0x56

0x52

0x52

0x52

0xE6

1 Byte WRITE

0x00

0x10

0x01

0x00

0x00

0x00

0xE7

1 Byte WRITE

0x00

0x00

0x00

0x75

0x75

0x75

0xE8

1 Byte WRITE

0x00

0x00

0x00

0x01

0x01

0x01

0xE9

1 Byte WRITE

0x00

0x00

0x00

0x0B

0x0B

0x0B

0xEA

1 Byte WRITE

0x00

0x00

0x00

0c54

0c54

0c54

0xEB

1 Byte WRITE

0x00

0x00

0x00

0x01

0x01

0x01

0xEC

1 Byte WRITE

0x00

0x00

0x00

0x04

0x04

0x04

0xED

1 Byte WRITE

0x00

0x00

0x00

0xB8

0xB8

0xB8

0xEE

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xEF

1 Byte WRITE

0x00

0x00

0x00

0x03

0x03

0x03

0xF0

1 Byte WRITE

0x00

0x00

0x00

0x70

0x70

0x70

0xF1

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

0xF2

1 Byte WRITE: switch on the burst mode?

0x00

0x00

0x00

0x01

0x01

0x01

0xF3

1 Byte WRITE : everytime 0x00

0x00

0x00

0x00

0x00

0x00

0x00

LCD display
In order to set the numbers on the LCD:
transfer type=bulk size=5 ep=0x02 dir=OUT
88 b3 00 01 00
transfer type=bulk size=5 ep=0x02 dir=OUT
88 da 00 01 a7
transfer type=bulk size=6 ep=0x02 dir=OUT
88 20 00 02 ef df

Registers 0x20 and 0x21 control 7 bits each of the LCD. The 8'th bit of 0x21
enables/disables the entire display:

Legend :
0x20 HGFEDCBA
0x21 76543210 (7 -enable)

         5

6              4

         0

  

        2

3              F

        G

 

1              D

         E

 

 

H             B

        C




Last update Oct. 25, 2004 Johannes Hub