Related
Has anybody ever worked with KDE inc. (EDM-4000 series) devices? Or with devices of "neighbor" series like KDM-47XY, KDM-57XY, etc.? EDM-4000 series (if exactly, my particular device is EDM-4954 VER :X1.01) is a motorized insertion type of MS & IC card Reader/Writer module with RS-232C interface for enterprise customers like banks. My problem is that I cannot get a successful ATR form the card, on power ON.
I send commands sequentially INIT - GET VERSION - ENTRY - STATUS REQUEST - IC POWER ON... But always receive status IC CARD NO ATR:
0X02 0X4E 0X38 0X31 0X31 0X33 0X03 0X46 0X0D
STX 'N' cm pm err0 err1 ETX BCC CR
0X31 0X33 err status means IC CARD NO ATR
Here is the full flow:
Opening port: /dev/ttyUSB0
0X02 0X43 0X30 0X30 0X33 0X32 0X34 0X30 0X33 0X30 0X30 0X30 0X30 0X31 0X30 0X03 0X77 0X0D
write INIT
2022/11/23 14:04:12 Write bytes: 18
Read attempt: 0
Start reader
Received:0X06 0X0D
reader: got data
2022/11/23 14:04:13 ACK received
write ENQ
2022/11/23 14:04:13 Write bytes: 2
Start reader
Received:0X02 0X50 0X30 0X30 0X30 0X31 0X32 0X32 0X32 0X31 0X32 0X03 0X63 0X0D
reader: got data
22212
INIT answer code is P ...OK
Track1: RW
Track2: RW
Track3: RW
JIS: RW
Support ICC: ICC able
0X02 0X43 0X3C 0X30 0X03 0X4C 0X0D
write get version type
2022/11/23 14:04:14 Write bytes: 7
Read attempt: 0
Start reader
Received:0X06 0X0D
reader: got data
2022/11/23 14:04:14 ACK received
write ENQ
2022/11/23 14:04:14 Write bytes: 2
Read attempt: 0
Start reader
Received:0X02 0X50 0X3C 0X30 0X30 0X31 0X28 0X20 0X45 0X44 0X4D 0X2D 0X34 0X39 0X35 0X34 0X20 0X56 0X45 0X52 0X20 0X3A 0X58 0X31 0X2E 0X30 0X31 0X20 0X29 0X03 0X0F 0X0D
reader: got data
( EDM-4954 VER :X1.01 )
0X02 0X43 0X32 0X31 0X03 0X43 0X0D
PLEASE INSERT A CARD AND PRESS A KEY
I got the byte [32] ( )
write ENTRY
2022/11/23 14:04:15 Write bytes: 7
Read attempt: 0
Start reader
Received:0X06 0X0D
reader: got data
2022/11/23 14:04:15 ACK received
write ENQ
2022/11/23 14:04:16 Write bytes: 2
Start reader
Received:0X02 0X50 0X32 0X31 0X30 0X32 0X03 0X52 0X0D
reader: got data
0X02 0X43 0X31 0X31 0X03 0X40 0X0D
write STATUS REQUEST /get char
I got the byte [32] ( )
2022/11/23 14:04:19 Write bytes: 7
Read attempt: 0
Start reader
Received:0X06 0X0D
reader: got data
2022/11/23 14:04:19 ACK received
write ENQ
2022/11/23 14:04:20 Write bytes: 2
Read attempt: 0
Start reader
Received:0X02 0X50 0X31 0X31 0X30 0X32 0X42 0X48 0X03 0X5B 0X0D
reader: got data
BH
0X02 0X43 0X38 0X31 0X03 0X49 0X0D
write IC POWER ON /get char
2022/11/23 14:04:20 Write bytes: 7
Read attempt: 0
Start reader
Received:0X06 0X0D
reader: got data
2022/11/23 14:04:20 ACK received
write ENQ
2022/11/23 14:04:20 Write bytes: 2
Start reader
Received:0X02 0X4E 0X38 0X31 0X31 0X33 0X03 0X46 0X0D
Unfortunately, such a behavior I have with all the IC cards I have, of various payment systems. What am I doing wrong? Or might my device be not OK? Cannot find any example, or community in the internet about these devices. Vendor also gives very few manuals. Might anybody have worked with this vendor? I work fine with a magnetic stripe, but I need ICC EMV mode now.
The USB HID spec states
Each top level collection must be an application collection and reports may not
span more than one top level collection.
However, one of the examples provided in the USB HID report descriptor tool, namely the display example, has a top-level collection of type logical with no application collection:
0x05, 0x14, // USAGE_PAGE (Alphnumeric Display)
0x09, 0x01, // USAGE (Alphanumeric Display)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0xa1, 0x02, // COLLECTION (Logical)
0x09, 0x20, // USAGE (Display Attributes Report)
0xa1, 0x02, // COLLECTION (Logical)
0x09, 0x35, // USAGE (Rows)
0x09, 0x36, // USAGE (Columns)
0x09, 0x3d, // USAGE (Character Width)
0x09, 0x3e, // USAGE (Character Height)
0x85, 0x01, // REPORT_ID (1)
0x25, 0x1f, // LOGICAL_MAXIMUM (31)
0x75, 0x05, // REPORT_SIZE (5)
0x95, 0x04, // REPORT_COUNT (4)
0xb1, 0x03, // FEATURE (Cnst,Var,Abs)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x03, // REPORT_COUNT (3)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x09, 0x21, // USAGE (ASCII Character Set)
0x09, 0x22, // USAGE (Data Read Back)
0x09, 0x29, // USAGE (Vertical Scroll)
0xb1, 0x03, // FEATURE (Cnst,Var,Abs)
0x95, 0x03, // REPORT_COUNT (3)
0xb1, 0x03, // FEATURE (Cnst,Var,Abs)
0xc0, // END_COLLECTION
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x25, 0x02, // LOGICAL_MAXIMUM (2)
0x09, 0x2d, // USAGE (Display Status)
0xa1, 0x02, // COLLECTION (Logical)
0x09, 0x2e, // USAGE (Stat Not Ready)
0x09, 0x2f, // USAGE (Stat Ready)
0x09, 0x30, // USAGE (Err Not a loadable character)
0x81, 0x40, // INPUT (Data,Ary,Abs,Null)
0xc0, // END_COLLECTION
0x09, 0x32, // USAGE (Cursor Position Report)
0xa1, 0x02, // COLLECTION (Logical)
0x85, 0x02, // REPORT_ID (2)
0x75, 0x04, // REPORT_SIZE (4)
0x95, 0x01, // REPORT_COUNT (1)
0x25, 0x0f, // LOGICAL_MAXIMUM (15)
0x09, 0x34, // USAGE (Column)
0xb1, 0x22, // FEATURE (Data,Var,Abs,NPrf)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x09, 0x33, // USAGE (Row)
0xb1, 0x22, // FEATURE (Data,Var,Abs,NPrf)
0xc0, // END_COLLECTION
0x09, 0x2b, // USAGE (Character Report)
0xa1, 0x02, // COLLECTION (Logical)
0x85, 0x03, // REPORT_ID (3)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x04, // REPORT_COUNT (4)
0x25, 0x7e, // LOGICAL_MAXIMUM (126)
0x09, 0x2c, // USAGE (Display Data)
0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf)
0xc0, // END_COLLECTION
0x85, 0x04, // REPORT_ID (4)
0x09, 0x3b, // USAGE (Font Report)
0xa1, 0x02, // COLLECTION (Logical)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x7e, // LOGICAL_MAXIMUM (126)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x09, 0x2c, // USAGE (Display Data)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x95, 0x05, // REPORT_COUNT (5)
0x09, 0x3c, // USAGE (Font Data)
0x92, 0x02, 0x01, // OUTPUT (Data,Var,Abs,Buf)
0xc0, // END_COLLECTION
0xc0, // END_COLLECTION
Is this allowed?
No, it is not allowed.
The example given seems to be quite similar to the example in "Appendix A.8 A Device with a Display" in the HID Usage Tables v1.12 specification which does have a top level application collection defined.
I'd say the example you posted may have some transcription errors - especially since the top level Usage selected (0x00140001) has Usage Type of CA and so is intended to be used on an Application Collection item rather than a Logical Collection (see page 109 of the Usage Tables document).
The first logical collection should be changed to:
0xa1, 0x01, // COLLECTION (Application)
It will then be parsed (albeit with bit alignment issues due to sections having been omitted from the original example) as:
//--------------------------------------------------------------------------------
// Decoded Application Collection
//--------------------------------------------------------------------------------
/*
05 14 (GLOBAL) USAGE_PAGE 0x0014 Alphanumeric Display Page
09 01 (LOCAL) USAGE 0x00140001 Alphanumeric Display (CA=Application Collection)
15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0 <-- Info: Consider replacing 15 00 with 14
A1 01 (MAIN) COLLECTION 0x00000001 Application (Usage=0x00140001: Page=Alphanumeric Display Page, Usage=Alphanumeric Display, Type=CA)
09 20 (LOCAL) USAGE 0x00140020 Display Attributes Report (CL=Logical Collection)
A1 02 (MAIN) COLLECTION 0x00000002 Logical (Usage=0x00140020: Page=Alphanumeric Display Page, Usage=Display Attributes Report, Type=CL)
09 35 (LOCAL) USAGE 0x00140035 Rows (SV=Static Value)
09 36 (LOCAL) USAGE 0x00140036 Columns (SV=Static Value)
09 3D (LOCAL) USAGE 0x0014003D Character Width (SV=Static Value)
09 3E (LOCAL) USAGE 0x0014003E Character Height (SV=Static Value)
85 01 (GLOBAL) REPORT_ID 0x01 (1)
25 1F (GLOBAL) LOGICAL_MAXIMUM 0x1F (31)
75 05 (GLOBAL) REPORT_SIZE 0x05 (5) Number of bits per field
95 04 (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields
B1 03 (MAIN) FEATURE 0x00000003 (4 fields x 5 bits) 1=Constant 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
75 01 (GLOBAL) REPORT_SIZE 0x01 (1) Number of bits per field
95 03 (GLOBAL) REPORT_COUNT 0x03 (3) Number of fields
25 01 (GLOBAL) LOGICAL_MAXIMUM 0x01 (1)
09 21 (LOCAL) USAGE 0x00140021 ASCII Character Set (SF=Static Flag)
09 22 (LOCAL) USAGE 0x00140022 Data Read Back (SF=Static Flag)
09 29 (LOCAL) USAGE 0x00140029 Vertical Scroll (SFDF=Static Flag or Dynamic Flag)
B1 03 (MAIN) FEATURE 0x00000003 (3 fields x 1 bit) 1=Constant 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
95 03 (GLOBAL) REPORT_COUNT 0x03 (3) Number of fields <-- Redundant: REPORT_COUNT is already 3
B1 03 (MAIN) FEATURE 0x00000003 (3 fields x 1 bit) 1=Constant 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
C0 (MAIN) END_COLLECTION Logical
75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field
95 01 (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields
25 02 (GLOBAL) LOGICAL_MAXIMUM 0x02 (2)
09 2D (LOCAL) USAGE 0x0014002D Display Status (CL=Logical Collection)
A1 02 (MAIN) COLLECTION 0x00000002 Logical (Usage=0x0014002D: Page=Alphanumeric Display Page, Usage=Display Status, Type=CL)
09 2E (LOCAL) USAGE 0x0014002E Stat Not Ready (Sel=Selector)
09 2F (LOCAL) USAGE 0x0014002F Stat Ready (Sel=Selector)
09 30 (LOCAL) USAGE 0x00140030 Err Not a loadable character (Sel=Selector)
81 40 (MAIN) INPUT 0x00000040 (1 field x 8 bits) 0=Data 0=Array 0=Absolute
C0 (MAIN) END_COLLECTION Logical
09 32 (LOCAL) USAGE 0x00140032 Cursor Position Report (CL=Logical Collection)
A1 02 (MAIN) COLLECTION 0x00000002 Logical (Usage=0x00140032: Page=Alphanumeric Display Page, Usage=Cursor Position Report, Type=CL)
85 02 (GLOBAL) REPORT_ID 0x02 (2)
75 04 (GLOBAL) REPORT_SIZE 0x04 (4) Number of bits per field
95 01 (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields <-- Redundant: REPORT_COUNT is already 1
25 0F (GLOBAL) LOGICAL_MAXIMUM 0x0F (15)
09 34 (LOCAL) USAGE 0x00140034 Column (DV=Dynamic Value)
B1 22 (MAIN) FEATURE 0x00000022 (1 field x 4 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 1=NoPrefState 0=NoNull 0=NonVolatile 0=Bitmap
25 01 (GLOBAL) LOGICAL_MAXIMUM 0x01 (1)
09 33 (LOCAL) USAGE 0x00140033 Row (DV=Dynamic Value)
B1 22 (MAIN) FEATURE 0x00000022 (1 field x 4 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 1=NoPrefState 0=NoNull 0=NonVolatile 0=Bitmap
C0 (MAIN) END_COLLECTION Logical
09 2B (LOCAL) USAGE 0x0014002B Character Report (CL=Logical Collection)
A1 02 (MAIN) COLLECTION 0x00000002 Logical (Usage=0x0014002B: Page=Alphanumeric Display Page, Usage=Character Report, Type=CL)
85 03 (GLOBAL) REPORT_ID 0x03 (3)
75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field
95 04 (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields
25 7E (GLOBAL) LOGICAL_MAXIMUM 0x7E (126)
09 2C (LOCAL) USAGE 0x0014002C Display Data (DV=Dynamic Value)
B2 0201 (MAIN) FEATURE 0x00000102 (4 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer
C0 (MAIN) END_COLLECTION Logical
85 04 (GLOBAL) REPORT_ID 0x04 (4)
09 3B (LOCAL) USAGE 0x0014003B Font Report (CL=Logical Collection)
A1 02 (MAIN) COLLECTION 0x00000002 Logical (Usage=0x0014003B: Page=Alphanumeric Display Page, Usage=Font Report, Type=CL)
15 00 (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0 <-- Info: Consider replacing 15 00 with 14
25 7E (GLOBAL) LOGICAL_MAXIMUM 0x7E (126) <-- Redundant: LOGICAL_MAXIMUM is already 126
75 08 (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field <-- Redundant: REPORT_SIZE is already 8
95 01 (GLOBAL) REPORT_COUNT 0x01 (1) Number of fields
09 2C (LOCAL) USAGE 0x0014002C Display Data (DV=Dynamic Value)
91 02 (MAIN) OUTPUT 0x00000002 (1 field x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
95 05 (GLOBAL) REPORT_COUNT 0x05 (5) Number of fields
09 3C (LOCAL) USAGE 0x0014003C Font Data (BB=Buffered Bytes)
92 0201 (MAIN) OUTPUT 0x00000102 (5 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 1=Buffer
C0 (MAIN) END_COLLECTION Logical
C0 (MAIN) END_COLLECTION Application
*/
//--------------------------------------------------------------------------------
// Alphanumeric Display Page featureReport 01 (Device <-> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x01 (1)
// Collection: AlphanumericDisplay DisplayAttributesReport
uint8_t AD_AlphanumericDisplayDisplayAttributesReportRows : 5; // Usage 0x00140035: Rows, Value = 0 to 31
uint8_t AD_AlphanumericDisplayDisplayAttributesReportColumns : 5; // Usage 0x00140036: Columns, Value = 0 to 31
uint8_t AD_AlphanumericDisplayDisplayAttributesReportCharacterWidth : 5; // Usage 0x0014003D: Character Width, Value = 0 to 31
uint8_t AD_AlphanumericDisplayDisplayAttributesReportCharacterHeight : 5; // Usage 0x0014003E: Character Height, Value = 0 to 31
uint8_t AD_AlphanumericDisplayDisplayAttributesReportAsciiCharacterSet : 1; // Usage 0x00140021: ASCII Character Set, Value = 0 to 1
uint8_t AD_AlphanumericDisplayDisplayAttributesReportDataReadBack : 1; // Usage 0x00140022: Data Read Back, Value = 0 to 1
uint8_t AD_AlphanumericDisplayDisplayAttributesReportVerticalScroll : 1; // Usage 0x00140029: Vertical Scroll, Value = 0 to 1
uint8_t : 1; // Pad
uint8_t : 1; // Pad
uint8_t : 1; // Pad
} featureReport01_t;
//--------------------------------------------------------------------------------
// Alphanumeric Display Page featureReport 02 (Device <-> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x02 (2)
// Collection: AlphanumericDisplay CursorPositionReport
uint8_t AD_AlphanumericDisplayCursorPositionReportColumn : 4; // Usage 0x00140034: Column, Value = 0 to 15
uint8_t AD_AlphanumericDisplayCursorPositionReportRow : 4; // Usage 0x00140033: Row, Value = 0 to 1
} featureReport02_t;
//--------------------------------------------------------------------------------
// Alphanumeric Display Page featureReport 03 (Device <-> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x03 (3)
// Collection: AlphanumericDisplay CharacterReport
uint8_t AD_AlphanumericDisplayCharacterReportDisplayData[4]; // Usage 0x0014002C: Display Data, Value = 0 to 126
} featureReport03_t;
//--------------------------------------------------------------------------------
// Alphanumeric Display Page inputReport 01 (Device --> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x01 (1)
// Collection: AlphanumericDisplay DisplayStatus
uint8_t AD_AlphanumericDisplayDisplayStatus; // Value = 0 to 2
// Value 0 = Usage 0x0014002E: Stat Not Ready
// Value 1 = Usage 0x0014002F: Stat Ready
// Value 2 = Usage 0x00140030: Err Not a loadable character
} inputReport01_t;
//--------------------------------------------------------------------------------
// Alphanumeric Display Page outputReport 04 (Device <-- Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x04 (4)
// Collection: AlphanumericDisplay FontReport
uint8_t AD_AlphanumericDisplayFontReportDisplayData; // Usage 0x0014002C: Display Data, Value = 0 to 126
uint8_t AD_AlphanumericDisplayFontReportFontData[5]; // Usage 0x0014003C: Font Data, Value = 0 to 126
} outputReport04_t;
I simply want to send from device to host with two report ID.These reports must have different Report Count(First report id has 4 report count, second report id has 40).This is what I have done so far:
//14 bytes
0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1)
0x09, 0x01, // USAGE (Vendor Usage 1)
0xa1, 0x01, // COLLECTION (Application)
// -------- common global items ---------
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
// 10 bytes | Input message 1 (sent from device to host)
0x85, 5, // Global Report ID (cannot be 0)
0x95, 4, // Global Report Count (number of Report Size fields)
0x19, 0x01, // USAGE_MINIMUM (Vendor Usage 1)
0x29, 5, // USAGE_MAXIMUM (Vendor Usage 64)
0x81, 0x02, // Main Input (data, array, absolute)
// 10 bytes | Input message 1 (sent from device to host)
0x85, 6, // Global Report ID (cannot be 0)
0x95, 40, // Global Report Count (number of Report Size fields)
0x19, 0x01, // USAGE_MINIMUM (Vendor Usage 1)
0x29, 41, // USAGE_MAXIMUM (Vendor Usage 64)
0x81, 0x02, // Main Input (data, array, absolute)
0xC0
But first report id is sending 40 bayt.Where is my mistake?
HID Terminal output:
R 02 0C 16 20 2A 34 3E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
R 01 0B 15 1F 29 34 3E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
I believe #Nipo has given you the correct answer: the report descriptor indicates what each report should look like, but it is still the responsibility of your code to send reports with the correct length specified.
For report id 5 that would be 5 (1 for the report id + 4 for the payload), and
for report it 6 it would be 41 (1 for the report id + 40 for the payload).
BTW, your report descriptor may need a little tweaking. As it stands, it decodes as:
//--------------------------------------------------------------------------------
// Decoded Application Collection
//--------------------------------------------------------------------------------
PROGMEM char usbHidReportDescriptor[] =
{
0x06, 0x00, 0xFF, // (GLOBAL) USAGE_PAGE 0xFF00 Vendor-defined
0x09, 0x01, // (LOCAL) USAGE 0xFF000001 <-- Warning: Undocumented usage
0xA1, 0x01, // (MAIN) COLLECTION 0x00000001 Application (Usage=0xFF000001: Page=Vendor-defined, Usage=, Type=)
0x15, 0x00, // (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0 <-- Info: Consider replacing 15 00 with 14
0x26, 0xFF, 0x00, // (GLOBAL) LOGICAL_MAXIMUM 0x00FF (255)
0x75, 0x08, // (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field
0x85, 0x05, // (GLOBAL) REPORT_ID 0x05 (5)
0x95, 0x04, // (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields
0x19, 0x01, // (LOCAL) USAGE_MINIMUM 0xFF000001 <-- Warning: Undocumented usage
0x29, 0x05, // (LOCAL) USAGE_MAXIMUM 0xFF000005 <-- Warning: Undocumented usage
0x81, 0x02, // (MAIN) INPUT 0x00000002 (4 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
0x85, 0x06, // (GLOBAL) REPORT_ID 0x06 (6)
0x95, 0x28, // (GLOBAL) REPORT_COUNT 0x28 (40) Number of fields
0x19, 0x01, // (LOCAL) USAGE_MINIMUM 0xFF000001 <-- Warning: Undocumented usage
0x29, 0x29, // (LOCAL) USAGE_MAXIMUM 0xFF000029 <-- Warning: Undocumented usage
0x81, 0x02, // (MAIN) INPUT 0x00000002 (40 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
0xC0, // (MAIN) END_COLLECTION Application
};
//--------------------------------------------------------------------------------
// Vendor-defined inputReport 05 (Device --> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x05 (5)
uint8_t VEN_VendorDefined0001; // Usage 0xFF000001: , Value = 0 to 255
uint8_t VEN_VendorDefined0002; // Usage 0xFF000002: , Value = 0 to 255
uint8_t VEN_VendorDefined0003; // Usage 0xFF000003: , Value = 0 to 255
uint8_t VEN_VendorDefined0004; // Usage 0xFF000004: , Value = 0 to 255
// Usage 0xFF000005 Value = 0 to 255 <-- Ignored: REPORT_COUNT (4) is too small
} inputReport05_t;
//--------------------------------------------------------------------------------
// Vendor-defined inputReport 06 (Device --> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x06 (6)
uint8_t VEN_VendorDefined0001; // Usage 0xFF000001: , Value = 0 to 255
uint8_t VEN_VendorDefined0002; // Usage 0xFF000002: , Value = 0 to 255
uint8_t VEN_VendorDefined0003; // Usage 0xFF000003: , Value = 0 to 255
uint8_t VEN_VendorDefined0004; // Usage 0xFF000004: , Value = 0 to 255
uint8_t VEN_VendorDefined0005; // Usage 0xFF000005: , Value = 0 to 255
uint8_t VEN_VendorDefined0006; // Usage 0xFF000006: , Value = 0 to 255
uint8_t VEN_VendorDefined0007; // Usage 0xFF000007: , Value = 0 to 255
uint8_t VEN_VendorDefined0008; // Usage 0xFF000008: , Value = 0 to 255
uint8_t VEN_VendorDefined0009; // Usage 0xFF000009: , Value = 0 to 255
uint8_t VEN_VendorDefined000A; // Usage 0xFF00000A: , Value = 0 to 255
uint8_t VEN_VendorDefined000B; // Usage 0xFF00000B: , Value = 0 to 255
uint8_t VEN_VendorDefined000C; // Usage 0xFF00000C: , Value = 0 to 255
uint8_t VEN_VendorDefined000D; // Usage 0xFF00000D: , Value = 0 to 255
uint8_t VEN_VendorDefined000E; // Usage 0xFF00000E: , Value = 0 to 255
uint8_t VEN_VendorDefined000F; // Usage 0xFF00000F: , Value = 0 to 255
uint8_t VEN_VendorDefined0010; // Usage 0xFF000010: , Value = 0 to 255
uint8_t VEN_VendorDefined0011; // Usage 0xFF000011: , Value = 0 to 255
uint8_t VEN_VendorDefined0012; // Usage 0xFF000012: , Value = 0 to 255
uint8_t VEN_VendorDefined0013; // Usage 0xFF000013: , Value = 0 to 255
uint8_t VEN_VendorDefined0014; // Usage 0xFF000014: , Value = 0 to 255
uint8_t VEN_VendorDefined0015; // Usage 0xFF000015: , Value = 0 to 255
uint8_t VEN_VendorDefined0016; // Usage 0xFF000016: , Value = 0 to 255
uint8_t VEN_VendorDefined0017; // Usage 0xFF000017: , Value = 0 to 255
uint8_t VEN_VendorDefined0018; // Usage 0xFF000018: , Value = 0 to 255
uint8_t VEN_VendorDefined0019; // Usage 0xFF000019: , Value = 0 to 255
uint8_t VEN_VendorDefined001A; // Usage 0xFF00001A: , Value = 0 to 255
uint8_t VEN_VendorDefined001B; // Usage 0xFF00001B: , Value = 0 to 255
uint8_t VEN_VendorDefined001C; // Usage 0xFF00001C: , Value = 0 to 255
uint8_t VEN_VendorDefined001D; // Usage 0xFF00001D: , Value = 0 to 255
uint8_t VEN_VendorDefined001E; // Usage 0xFF00001E: , Value = 0 to 255
uint8_t VEN_VendorDefined001F; // Usage 0xFF00001F: , Value = 0 to 255
uint8_t VEN_VendorDefined0020; // Usage 0xFF000020: , Value = 0 to 255
uint8_t VEN_VendorDefined0021; // Usage 0xFF000021: , Value = 0 to 255
uint8_t VEN_VendorDefined0022; // Usage 0xFF000022: , Value = 0 to 255
uint8_t VEN_VendorDefined0023; // Usage 0xFF000023: , Value = 0 to 255
uint8_t VEN_VendorDefined0024; // Usage 0xFF000024: , Value = 0 to 255
uint8_t VEN_VendorDefined0025; // Usage 0xFF000025: , Value = 0 to 255
uint8_t VEN_VendorDefined0026; // Usage 0xFF000026: , Value = 0 to 255
uint8_t VEN_VendorDefined0027; // Usage 0xFF000027: , Value = 0 to 255
uint8_t VEN_VendorDefined0028; // Usage 0xFF000028: , Value = 0 to 255
// Usage 0xFF000029 Value = 0 to 255 <-- Ignored: REPORT_COUNT (40) is too small
} inputReport06_t;
You may want to try the following instead:
//--------------------------------------------------------------------------------
// Decoded Application Collection
//--------------------------------------------------------------------------------
PROGMEM char usbHidReportDescriptor[] =
{
0x06, 0x00, 0xFF, // (GLOBAL) USAGE_PAGE 0xFF00 Vendor-defined
0x09, 0x01, // (LOCAL) USAGE 0xFF000001 <-- Warning: Undocumented usage
0xA1, 0x01, // (MAIN) COLLECTION 0x00000001 Application (Usage=0xFF000001: Page=Vendor-defined, Usage=, Type=)
0x15, 0x00, // (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0 <-- Info: Consider replacing 15 00 with 14
0x26, 0xFF, 0x00, // (GLOBAL) LOGICAL_MAXIMUM 0x00FF (255)
0x75, 0x08, // (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field
0x85, 0x05, // (GLOBAL) REPORT_ID 0x05 (5)
0x95, 0x04, // (GLOBAL) REPORT_COUNT 0x04 (4) Number of fields
0x19, 0x01, // (LOCAL) USAGE_MINIMUM 0xFF000001 <-- Warning: Undocumented usage
0x29, 0x04, // (LOCAL) USAGE_MAXIMUM 0xFF000004 <-- Warning: Undocumented usage
0x81, 0x00, // (MAIN) INPUT 0x00000000 (4 fields x 8 bits) 0=Data 0=Array 0=Absolute 0=Ignored 0=Ignored 0=PrefState 0=NoNull
0x85, 0x06, // (GLOBAL) REPORT_ID 0x06 (6)
0x95, 0x28, // (GLOBAL) REPORT_COUNT 0x28 (40) Number of fields
0x19, 0x01, // (LOCAL) USAGE_MINIMUM 0xFF000001 <-- Warning: Undocumented usage
0x29, 0x28, // (LOCAL) USAGE_MAXIMUM 0xFF000028 <-- Warning: Undocumented usage
0x81, 0x00, // (MAIN) INPUT 0x00000000 (40 fields x 8 bits) 0=Data 0=Array 0=Absolute 0=Ignored 0=Ignored 0=PrefState 0=NoNull
0xC0, // (MAIN) END_COLLECTION Application
};
//--------------------------------------------------------------------------------
// Vendor-defined inputReport 05 (Device --> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x05 (5)
uint8_t VEN_VendorDefined[4]; // Value = 0 to 255
} inputReport05_t;
//--------------------------------------------------------------------------------
// Vendor-defined inputReport 06 (Device --> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x06 (6)
uint8_t VEN_VendorDefined[40]; // Value = 0 to 255
} inputReport06_t;
I am programming an RS-485 protocol on a PIC micro controller and Linux computer. I was originally thinking about using CRC8 to check incoming data, however it looks like this would be a processor intensive task.
Instead I was thinking of a more simple PEC algorithm, perhaps XORing all the incoming bytes with a seed to create a very simple single step implementation of CRC.
What downside would having an algorithm such as this be?
A CRC is not processor-intensive. All it adds to your exclusive-or is a table lookup. The operation on each byte is simply: crc = crc8_table[crc ^ *data++]. See below.
The downside of doing just an exclusive-or is that there are many simple errors that cancel each other, resulting in a false-positive check. A CRC is much better.
#include <stddef.h>
/* 8-bit CRC with polynomial x^8+x^6+x^3+x^2+1, 0x14D.
Chosen based on Koopman, et al. (0xA6 in his notation = 0x14D >> 1):
http://www.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf
*/
static unsigned char crc8_table[] = {
0x00, 0x3e, 0x7c, 0x42, 0xf8, 0xc6, 0x84, 0xba, 0x95, 0xab, 0xe9, 0xd7,
0x6d, 0x53, 0x11, 0x2f, 0x4f, 0x71, 0x33, 0x0d, 0xb7, 0x89, 0xcb, 0xf5,
0xda, 0xe4, 0xa6, 0x98, 0x22, 0x1c, 0x5e, 0x60, 0x9e, 0xa0, 0xe2, 0xdc,
0x66, 0x58, 0x1a, 0x24, 0x0b, 0x35, 0x77, 0x49, 0xf3, 0xcd, 0x8f, 0xb1,
0xd1, 0xef, 0xad, 0x93, 0x29, 0x17, 0x55, 0x6b, 0x44, 0x7a, 0x38, 0x06,
0xbc, 0x82, 0xc0, 0xfe, 0x59, 0x67, 0x25, 0x1b, 0xa1, 0x9f, 0xdd, 0xe3,
0xcc, 0xf2, 0xb0, 0x8e, 0x34, 0x0a, 0x48, 0x76, 0x16, 0x28, 0x6a, 0x54,
0xee, 0xd0, 0x92, 0xac, 0x83, 0xbd, 0xff, 0xc1, 0x7b, 0x45, 0x07, 0x39,
0xc7, 0xf9, 0xbb, 0x85, 0x3f, 0x01, 0x43, 0x7d, 0x52, 0x6c, 0x2e, 0x10,
0xaa, 0x94, 0xd6, 0xe8, 0x88, 0xb6, 0xf4, 0xca, 0x70, 0x4e, 0x0c, 0x32,
0x1d, 0x23, 0x61, 0x5f, 0xe5, 0xdb, 0x99, 0xa7, 0xb2, 0x8c, 0xce, 0xf0,
0x4a, 0x74, 0x36, 0x08, 0x27, 0x19, 0x5b, 0x65, 0xdf, 0xe1, 0xa3, 0x9d,
0xfd, 0xc3, 0x81, 0xbf, 0x05, 0x3b, 0x79, 0x47, 0x68, 0x56, 0x14, 0x2a,
0x90, 0xae, 0xec, 0xd2, 0x2c, 0x12, 0x50, 0x6e, 0xd4, 0xea, 0xa8, 0x96,
0xb9, 0x87, 0xc5, 0xfb, 0x41, 0x7f, 0x3d, 0x03, 0x63, 0x5d, 0x1f, 0x21,
0x9b, 0xa5, 0xe7, 0xd9, 0xf6, 0xc8, 0x8a, 0xb4, 0x0e, 0x30, 0x72, 0x4c,
0xeb, 0xd5, 0x97, 0xa9, 0x13, 0x2d, 0x6f, 0x51, 0x7e, 0x40, 0x02, 0x3c,
0x86, 0xb8, 0xfa, 0xc4, 0xa4, 0x9a, 0xd8, 0xe6, 0x5c, 0x62, 0x20, 0x1e,
0x31, 0x0f, 0x4d, 0x73, 0xc9, 0xf7, 0xb5, 0x8b, 0x75, 0x4b, 0x09, 0x37,
0x8d, 0xb3, 0xf1, 0xcf, 0xe0, 0xde, 0x9c, 0xa2, 0x18, 0x26, 0x64, 0x5a,
0x3a, 0x04, 0x46, 0x78, 0xc2, 0xfc, 0xbe, 0x80, 0xaf, 0x91, 0xd3, 0xed,
0x57, 0x69, 0x2b, 0x15};
unsigned crc8(unsigned crc, unsigned char *data, size_t len)
{
unsigned char *end;
if (len == 0)
return crc;
crc ^= 0xff;
end = data + len;
do {
crc = crc8_table[crc ^ *data++];
} while (data < end);
return crc ^ 0xff;
}
/* this was used to generate the table and to test the table-version
#define POLY 0xB2
unsigned crc8_slow(unsigned crc, unsigned char *data, size_t len)
{
unsigned char *end;
if (len == 0)
return crc;
crc ^= 0xff;
end = data + len;
do {
crc ^= *data++;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
} while (data < end);
return crc ^ 0xff;
}
*/
#include <stdio.h>
#define SIZE 16384
int main(void)
{
unsigned char data[SIZE];
size_t got;
unsigned crc;
crc = 0;
do {
got = fread(data, 1, SIZE, stdin);
crc = crc8(crc, data, got);
} while (got == SIZE);
printf("%02x\n", crc);
return 0;
}
The table look up method for generating the PEC value is certainly faster. My tests on a a PIC32 running at 80 MHz for a 4 byte packet indicates that the table method required 2.8us while the algorithm method needed 11.5us. The memory requirements shows the cost of of speed: The table method requires 348 bytes while the algorithm method on;y 216 bytes. So if memory is scarce - consider the algorithm approach shown here. (BYTE is an unsigned char)
/* bit_crc8 FUNCTION DESCRIPTION ************************************
* SYNTAX: BYTE bit_crc8( BYTE *data, BYTE len);
* KEYWORDS: PEC, CRC, error checking
* DESCRIPTION: Returns the PEC for an array of bytes. This method does
* not use a lookup table
* PARAMETER 1: BYTE pointer to data array
* PARAMETER 2: BYTE - Number of bytes in array
* RETURN VALUE: BYTE - PEC value
* NOTES: SMBus limits the number of bytes in the packet to 256
* Primitive polynomial is set by the definition of PEC.
* END DESCRIPTION **********************************************************/
BYTE bit_crc8( BYTE *data, BYTE len)
{
#define PEC 0x07 // Implements Polynomial X^8 + X^2 + X^1 +1
BYTE crc = 0;
BYTE loop, b8;
while (len--)
{
crc ^= *data++;
for(loop=0; loop <8; loop++)
{
b8 = crc & 0x80; /* Test for MSB set to 1 */
crc <<= 1; /* Left shift CRC */
if(b8)
{
crc ^= PEC; /* Divide by PEC if bit 8 was set */
}
}
}
return crc;
}
I've stumbled across a problem way beyond my area of expertise, and I don't have a mentor to turn to for help with this.
I have a receipt printer I need to interface with through an iOS app. The printer is located on the same network as the device(s), so I can address it through supported "Line Mode commands"
What I'd like to do is keep the code I have already that works cross-platform – i.e. it's a UIView/NSView, and if you're not familiar with OS X/iOS, it's just a standard vanilla view that I can render into PDF/PNG formats. Thankfully, the printer has a "raster graphics" mode that seems to be what I need.
Unfortunately, be it the broken English of the command spec, or my complete lack of knowledge of anything beyond basic C, or my complete lack of knowledge regarding graphics, I have no idea how to even get started from the command specifications I have. I know the printer and my networking works because I can address it over the network and send it basic feed commands. But, I have no idea how to go from a PNG -> whatever the printer needs to make it's 'raster mode' work.
The specification is available at http://www.star-m.jp/eng/service/usermanual/linemode_cm_en.pdf , and the page you'd want to start reading it if you want to help is 3-68, and the specific commands I'm having trouble even getting started with are on 3-78/3-79.
I can give you nothing but a checkmark but I assure you, you'll have my undying gratitude if you can even provide me even a point in the right direction.
Having written a few printer drivers I can confirm that generally the documentation is confusing because of the way printers work. The document that you refer to doesn't actually seem to bad to me.
I think you're right to be printing in raster mode and that overall this is going to give the best results.
From the Star documentation I reckon you'll need to send :
1. \x1b*rR Initialize raster mode
2. \x1b*rA Enter raster mode
3. \x1b*rC Clear raster data
4. \x1b*rml
4. b\x##\x##\xAA\xAA\xAA....<DATA>..........
5. \x1b\x0C\x00 Raster Form feed(??) - should spit out the data.
6. \x1b*rB Clear raster data
Obv. in the above \x1b is the C encoding of ESC (i.e. character 27 0x1b).
From all of the documentation that I've been reading the following is how the images should be formatted in raster mode. When in line mode it is completely different as the vertical & horizontal are swapped. From THERMAL PRINTER PROGRAMMER'S MANUAL (TSP552,TSP552II,TSP2000)
This equates to the following bytes stream.
On the 4th command line it is effectively 'b' followed by two bytes definining the size. This size is computed as the number of pixels contained in the stream % 256 and / 256. So for 320x1 that'd 0x40,0x01
So, taking the above and plugging it into a simple test program you should test with this:
char rasterImage [] = {
0x1b, '*', 'r', 'R', // Initialize raster mode
0x1b, '*', 'r', 'A', // Enter raster mode
0x1b, '*', 'r', 'C', // Clear raster data
// n1 n2 d1 d2..
0x1b, 'b', 0x2, 0, 0x00, 0x00, // data
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x3F, 0xFC,
0x1b, 'b', 0x2, 0, 0x77, 0xEE,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0x0F, 0xF0,
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x3E, 0x7C,
0x1b, 'b', 0x2, 0, 0x38, 0x1C,
0x1b, 'b', 0x2, 0, 0x79, 0x9E,
0x1b, 'b', 0x2, 0, 0x73, 0xCE,
0x1b, 'b', 0x2, 0, 0x73, 0xCE,
0x1b, 'b', 0x2, 0, 0xF9, 0x9F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xFE, 0x7F,
0x1b, 'b', 0x2, 0, 0xFF, 0xFF,
0x1b, 'b', 0x2, 0, 0xFF, 0xFF,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00};
[self.currentDataBeingSent appendBytes:rasterImage length:sizeof(rasterImage)];
Simply squirt that out to the printer and you should get a picture as above. This is where you can easily tweak and play about with the exact commands to get something that's working. Often this is the only way I've ever managed to figure out what should be done.
rev.3
Ref. comments.
If you have a byte per pixel then you will need to merge these into a series of bits; the following should do the job based on your pastebin code. I've also changed the char* to be unsigned as it is signed can cause problems when bit manipulating.
NSUInteger bitmapBytePerRow = width/8;
NSUInteger bytesPerRow = 3 + bitmapBytePerRow;
[self.currentDataBeingSent = [NSMutableData dataWithLength:bytesPerRow * height];
[self.currentDataBeingSent appendBytes:initializeRaster length:sizeof(initializeRaster)];
[self.currentDataBeingSent appendBytes:enterRaster length:sizeof(enterRaster)];
NSUInteger byteOffset = 0;
for (NSUInteger y = 0; y < height; y++)
{
unsigned char *rasterCommandForRow = (unsigned char *)calloc(bytesPerRow, sizeof(char));
unsigned char *current_raster = rasterCommandForRow;
*current_raster++ = '\x6B';
*current_raster++ = (width*height) % 256;
*current_raster++ = (width*height) / 256;
unsigned char mask = '\x80' ;
unsigned char out = 0 ;
for (NSUInteger x = 0; x < width; x++)
{
if (*(data + (byteOffset * sizeof(char))))
out |= mask ;
byteOffset++;
mask >>= 1 ;
if( 0 == mask )
{
mask = '\x80' ;
*current_raster++ = out ;
if( out )
lastDot = nextOut ;
out = 0 ;
}
}
// handle partially finished byte .
if( ( '\x80' != mask ) && ( 0 != out ) )
*current_raster++ = out ;
[self.currentDataBeingSent appendBytes:rasterCommandForRow length:bytesPerRow];
}
rev.3a
Looking at the Mac CUPS support from Star it's got the source code for the driver which contains a lot of clues about how this should be done. Sometimes code is so much easier to read than documentation.
starcupsdrv-3.1.1_mac_20100423.zip\starcupsdrv-3.1.1_mac\SourceCode\
contains starcupsdrv-src-3.1.1.tar.gz\ sub folder starcupsdrv\src\
View rastertostar.c, the important bit is the calculation of the n1 / n2 values. These aren't at all X & Y but based on the pixel count, lastBlackPixel is the count of pixels from the source.
putchar('b');
putchar((char) ((lastBlackPixel > 0)?(lastBlackPixel % 256):1));
putchar((char) (lastBlackPixel / 256));
I've modified the code above to include the fixes, hopefully that'll be closer. If not post a scan of what comes out of the printer, it will be useful to diagnose what's happening.
For reference The code between 580:650 from jsStarUSB.cpp seems to me to be along the lines of what you need to produce a buffer (stored in nextOut) that contains the raster data in the format to be sent directly to the printer.
rev. 4 (2023)
Joshua May advises in the comments that rasterImage doesn't need the ESC (0x1b) on each row - only the 'b' and that by just removing the 0x1b "worked for me".
I have a hunch this might be the same as the old Seiko printers, only yours is network enabled. If so, have a look at the C code here. It tries to output to a serial port /dev/cua, where it thinks the printer is.
But if the commands are the same, the code should help you. It takes as input the Portable Bitmap Format, which is plain ASCII text.
But I don't know. Microsoft indicates Star Micronics works the same as Epson LQ, in which case there is ample documentation.
Related links:
ESC/POS PDF Documentation, hundreds of pages
Command codes from the STAR website
Update! ;-) Try this, totally untested code:
/* Call with grayscale images of height 256, width 256. */
- (void) outputraster(char* pixels, int rows)
{
const char initializeRaster[] = "\x1B\x2A\x72\x52";
const char enterRaster[] = "\x1B\x2A\x72\x41";
const char formFeed[] = "\x1B\x0C\x00";
const char clearRaster[] = "\x1B\x2A\x72\x43";
const char exitRaster[] = "\x1B\x2A\x72\x42";
/* The FF means 255 lines: */
char setRasterPageLength[] "\x1B\x2A\x72\x50\xFF\x0";
/* The FF FF means 256 lines and 256 rows: */
char sendRasterData[] = "\x62\xFF\xFF";
[self sendBytes:initializeRaster ofLength:sizeof(initializeRaster)];
[self sendBytes:enterRaster ofLength:sizeof(enterRaster)];
[self sendBytes:clearRaster ofLength:sizeof(clearRaster)];
[self sendBytes:setRasterPageLength ofLength:sizeof(setRasterPageLength)];
[self sendBytes:sendRasterData ofLength:sizeof(sendRasterData)];
while (rows)
{
for (int x = 0; x < 255; x++)
{
[self sendBytes:pixels[x] ofLength:256];
}
rows --;
}
}
Update!
I was looking at the docs at night, and stumbled upon how you can print out a prestored logo. Then I looked at how to define that logo, and that part of the documentation looked a lot more thorough:
Explanations of bitmap format for a similar printer:
Also, look at pages 34 and on for an explanation of the bitmap format of a Star printer.
I hope this helps someone, but I was trying to use the code from Richard Harrison above to print the phone raster in Python...
I on my system I can run python3 raster.py > /dev/usb/lp0 and I get the expected output!! Hopefully it shows what you need. I cross refrenced the bytes of a file that would print, and also the graphical mode manual.
import sys
buf = [
0x1b, ord('*'), ord('r'), ord('A'), # enter raster mode
0x1b, ord('*'), ord('r'), ord('P'), ord('0'), 0x00, # continuous mode
ord('b'), 0x2, 0, 0x00, 0x00,
ord('b'), 0x2, 0, 0x1F, 0xF8,
ord('b'), 0x2, 0, 0x3F, 0xFC,
ord('b'), 0x2, 0, 0x77, 0xEE,
ord('b'), 0x2, 0, 0xF8, 0x1F,
ord('b'), 0x2, 0, 0xF8, 0x1F,
ord('b'), 0x2, 0, 0xF8, 0x1F,
ord('b'), 0x2, 0, 0x0F, 0xF0,
ord('b'), 0x2, 0, 0x1F, 0xF8,
ord('b'), 0x2, 0, 0x1F, 0xF8,
ord('b'), 0x2, 0, 0x3E, 0x7C,
ord('b'), 0x2, 0, 0x38, 0x1C,
ord('b'), 0x2, 0, 0x79, 0x9E,
ord('b'), 0x2, 0, 0x73, 0xCE,
ord('b'), 0x2, 0, 0x73, 0xCE,
ord('b'), 0x2, 0, 0xF9, 0x9F,
ord('b'), 0x2, 0, 0xF8, 0x1F,
ord('b'), 0x2, 0, 0xFE, 0x7F,
ord('b'), 0x2, 0, 0xFF, 0xFF,
ord('b'), 0x2, 0, 0xFF, 0xFF,
ord('b'), 0x2, 0, 0x00, 0x00,
ord('b'), 0x2, 0, 0x00, 0x00,
ord('b'), 0x2, 0, 0x00, 0x00,
ord('b'), 0x2, 0, 0x00, 0x00,
0x1b, ord('*'), ord('r'), ord('b') # end raster mode
]
blob = bytearray(buf)
sys.stdout.buffer.write(blob)
EDIT: Off the back of this, I created a Python library that will take an image and convert it into the required raster commands... https://pypi.org/project/StarTSPImage/