Omron CJ1 DM Memory: 16-bit Words and Byte Operations
Overview: The 16-bit Word Memory Model
Omron CJ1 and CJ2 PLCs organize all I/O, work, and data memory in 16-bit words. The DM (Data Memory) area is no exception: each DM location such as D0, D1, or D1000 is a 16-bit word, not a single byte. Attempting to address a single byte using a syntax like D0.0 (low byte) or D0.1 (high byte) is not valid in the CJ1 or CJ2 instruction set. The smallest addressable unit within a DM word is the bit, written as D0.00 through D0.15.
This architectural decision traces back to the Omron V8 PLC introduced in 1978, and it has been preserved through the C-series, CV-series, CS1, CJ1, and CJ2 product lines. The motivation is twofold: bus efficiency (one 16-bit fetch retrieves two bytes simultaneously) and instruction-set simplicity (a single word-oriented ALU handles most arithmetic and logical operations). Modern CJ-series CPUs operate with abundant memory, but the word-oriented model remains for backward compatibility and execution speed.
DM Word Structure
Why 16-bit Words Instead of Bytes
Three engineering reasons drive the 16-bit organization in Omron PLCs:
- Bus width alignment: The internal data bus in CJ-series CPUs is 16 bits wide. A word-sized fetch completes in one bus cycle; an 8-bit fetch would either waste bus capacity or require additional byte-enable logic.
- Instruction execution speed: Word-oriented arithmetic (ADD, SUB, MUL, DIV) and logical operations (ANDW, ORW, XORW) operate on 16-bit data in a single CPU cycle. Byte-masked versions would either operate on the same 16-bit word internally or require slower microcoded routines.
- Backward compatibility: Every Omron PLC since 1978 has used the word-oriented model. Changing the addressing scheme would break the installed base of programs in long-life applications such as utilities, water/wastewater, and process control.
For serial communication applications where individual bytes must be transmitted or received, the word-oriented model introduces an impedance mismatch. The TXD (Transmit) and RXD (Receive) instructions in CJ-series PLCs expect word-sized data; transmitting one byte at a time requires packing two bytes into a single DM word and managing the high-byte/low-byte boundary manually.
DM Memory Map in CJ1 and CJ2 CPUs
The DM area in CJ-series PLCs is a contiguous block of 16-bit words, accessible through the D prefix in CX-Programmer. The address range and capacity depend on the CPU model:
| CPU Model | DM Area Range | Word Count | Retentive |
|---|---|---|---|
| CJ1M-CPU11/12/13 | D0 to D32767 | 32,768 | Yes (battery) |
| CJ1M-CPU21/22/23 | D0 to D32767 | 32,768 | Yes (battery) |
| CJ1G-CPU42/43/44/45 | D0 to D32767 | 32,768 | Yes (battery) |
| CJ1H-CPU65/66/67/68 | D0 to D32767 | 32,768 | Yes (battery) |
| CJ2M-CPU11/12/13/14/15 | D0 to D32767 | 32,768 | Yes (battery) |
| CJ2H-CPU64/65/66/67/68 | D0 to D32767 | 32,768 | Yes (battery) |
Extended Memory (EM) banks are available in some CJ2 CPUs and provide additional word-addressable storage in 32K-word banks. EM banks use the same 16-bit word organization as DM and are addressed with the E prefix (e.g., E0_0, E0_1).
Bit-Level Access: D0.00 to D0.15
While byte access is not available, bit access within a DM word is fully supported. The notation D0.00 refers to the least-significant bit (LSB) of word D0; D0.15 refers to the most-significant bit (MSB). The following example demonstrates setting and clearing individual bits in a ladder program:
LD P_On
OR D100.00
ANDNOT D100.01
OUT D100.00 ; Set bit 0 of D100
OUT D100.15 ; Set bit 15 of D100
Bit access is useful for flag storage, individual status bits, and boolean memory locations. It is not, however, a substitute for byte access in serial communication buffers, ASCII storage, or any application that requires 8-bit data integrity.
Word-to-Byte Workaround: ANDW Masking
The standard technique for extracting or isolating a single byte from a 16-bit DM word is the ANDW (AND Word) instruction. The format is:
ANDW S1 S2 D
Where S1 and S2 are the source words to be ANDed, and D is the destination word. The result is the bitwise AND of S1 and S2 stored in D. To use a constant mask, prefix the value with # for hexadecimal or & for decimal.
Isolating the Low Byte (bits 0-7)
To extract the low byte of D1000 and place it in D500:
ANDW D1000 #00FF D500
The mask #00FF (hexadecimal 0x00FF) clears bits 8-15 and preserves bits 0-7. The result in D500 contains the low byte in the lower 8 bits and 0x00 in the upper 8 bits.
Isolating the High Byte (bits 8-15)
To extract the high byte of D1000 and place it in D500:
ANDW D1000 #FF00 D500
The mask #FF00 (hexadecimal 0xFF00) clears bits 0-7 and preserves bits 8-15. The result in D500 contains the high byte in the upper 8 bits and 0x00 in the lower 8 bits.
Shifting the High Byte to the Low Byte Position
To shift the high byte into the low byte position (so the byte can be transmitted or compared as an 8-bit value), use either division or rotation:
Method 1: Division
ANDW D1000 #FF00 D500 ; Mask to high byte only (result: 0xHH00)
DIV D500 #0100 D502 ; Divide by 256, quotient in D502
; D502 now contains 0x00HH (high byte shifted to low byte position)
Method 2: Eight rotations
ANDW D1000 #FF00 D500 ; Mask to high byte only
ROR D500 ; Rotate right by 1 bit
ROR D500
ROR D500
ROR D500
ROR D500
ROR D500
ROR D500
ROR D500 ; After 8 rotations, high byte is in low byte position
Practical Example: TXD/RXD Serial Byte Packing
The most common scenario requiring byte access in a CJ1 or CJ2 is serial communication using the TXD (Transmit) and RXD (Receive) instructions. These instructions transfer 16-bit words; if the connected device sends or expects 8-bit data, the program must pack or unpack bytes manually.
Scenario
A CJ1M-CPU12 communicates with an RFID reader over RS-232 at 9600 baud, 8 data bits, no parity, 1 stop bit. The reader sends ASCII command responses terminated by CR (0x0D). The PLC must receive the string and parse it character by character.
Receive Unpack Method
- RXD instruction places a 16-bit word into D2000. Two ASCII characters are packed into this word: the first character in the low byte, the second in the high byte.
- Extract character 1 (low byte):
ANDW D2000 #00FF D2010 - Extract character 2 (high byte):
ANDW D2000 #FF00 D2020followed byDIV D2020 #0100 D2021to shift it to the low byte position. - Increment the receive counter and continue until the terminator (0x0D) is detected.
Transmit Pack Method
To pack two ASCII characters from D3000 (low byte) and D3001 (low byte) into a single word D3010 for transmission:
ANDW D3000 #00FF D3020 ; Mask D3000 low byte
MUL D3020 #0100 D3020 ; Shift left 8 positions (multiply by 256)
ANDW D3001 #00FF D3030 ; Mask D3001 low byte
ORW D3020 D3030 D3010 ; OR the two halves together
TXD D3010 D3011 #0001 ; Transmit 1 word
The TXD instruction control word format (D3011) depends on the serial port configuration and CX-Programmer version. For RS-232C port 1 on CJ1M-CPU12, a typical control word is #0001 (start transmission, 1 word). Refer to the CX-Programmer Operation Manual for the exact control word format for your serial port configuration.
Alternative Methods: Stack Pointers and Index Registers
For applications with thousands of bytes (barcode data, long ASCII strings, file transfers), a stack-pointer approach is more maintainable than per-byte mask-and-store:
; Receive loop using index register IR0
LD RXD_Complete
MOV #0 IR0
LOOP:
ANDW D_RXDBuf[IR0] #00FF D_String[IR0]
; D_RXDBuf is the RXD buffer; D_String is the parsed string storage
INC IR0
CMP IR0 #1000
BL LOOP
This method wastes the high byte of every word in D_String (50% memory inefficiency) but uses only one ANDW operation per byte, avoiding rotation overhead. For a 1000-byte string, this consumes 1000 words of DM versus the 500-word consumption of a packed scheme, but the code is significantly simpler and easier to maintain.
For packed storage using an index step of 2:
; Pack two bytes into one word using IR0 with step 2
LD ByteReady
ANDW D_SourceA #00FF D_Temp
MUL D_Temp #0100 D_Temp
ANDW D_SourceB #00FF D_Temp2
ORW D_Temp D_Temp2 D_Packed[IR0]
ADD IR0 #2
This reduces DM consumption by 50% but adds four instructions per byte. Choose packed storage when DM space is critical; choose unpacked storage when scan time and code clarity are priorities.
Comparison: Byte Access Across PLC Platforms
| Platform | Smallest Addressable Unit | Byte Access Syntax | Workaround Required |
|---|---|---|---|
| Omron CJ1/CJ2 | Bit (D0.00) | Not native | ANDW with mask |
| Siemens S7-200 | Byte (VB100) | VB100, MB0 | None |
| Siemens S7-1200/S7-1500 | Byte (DB1.DBB0) | DB1.DBB0, MB0 | None |
| Siemens S5 (legacy) | Byte (FY0) | FY0, MB0 | None |
| Allen-Bradley SLC 500 | Bit (B3:0/0) | Not native | MVM (Masked Move) |
| Allen-Bradley MicroLogix | Bit (B3:0/0) | Not native | MVM (Masked Move) |
| Allen-Bradley ControlLogix | Bit (tag.0) | Direct: tag.0 | None for SINT type |
| Mitsubishi FX Series | Bit (M0) | Limited | BMOV or MOV with mask |
| Mitsubishi Q/L Series | Bit (M0) | Limited | BMOV or MOV with mask |
| Schneider Modicon M340 | Byte (%MB0) | %MB0, %MW0.X | None |
Omron's approach is consistent with the older C-series heritage. Allen-Bradley's SLC 500 and MicroLogix lines have the same constraint; ControlLogix's tag-based model provides more flexibility through SINT (8-bit) data types. Siemens S7 platforms offer the most native byte access across all product generations.
Troubleshooting Matrix: Common Byte-Access Issues
| Symptom | Likely Cause | Diagnostic Step | Fix |
|---|---|---|---|
| Received data shifted by 1 byte | High-byte/low-byte packing reversed | Force D_RXDBuf to 0x4142, verify D2010 = 0x42 (B), D2021 = 0x41 (A) | Swap mask: use #00FF for first char, #FF00 for second |
| Transmitted data has extra zero bytes | High byte not masked before TXD | Watch D3010 in online mode; verify upper 8 bits = 0 | Add ANDW D3010 #00FF D3010 before TXD |
| ASCII string terminator not detected | Comparison uses 16-bit value instead of 8-bit | Watch terminator value; verify = 0x000D after mask | Always apply ANDW #00FF before CMP with 0x0D |
| Loop overruns DM boundary | Index register not initialized | Monitor IR0; verify starts at 0 | Add MOV #0 IR0 at scan start or first scan flag |
| Garbled data on RS-485 multi-drop | Byte packing across DM word boundary | Verify two-byte pair stays within one word; do not split pairs | Use even index values for packed storage |
| Scan time increases significantly | Rotation loop executed per byte | Profile scan time with CX-Programmer trace | Replace ROR x 8 with single DIV #0100 |
Verification and Field Testing
After implementing byte-access logic in a CJ1/CJ2 program, verify the following using CX-Programmer (version 9.x or later recommended):
- Online monitoring: Place the ANDW instruction on a watch window. Force D1000 to a known value (e.g., 16#1234). Verify that D500 shows 16#1200 after the high-byte mask and 16#0034 after the low-byte mask.
- RXD capture test: Connect a terminal emulator to the serial port. Send a known string. Verify that the unpacked bytes match the transmitted characters by comparing to a reference table.
- Loop counter check: For index-register-based loops, verify that IR0 increments correctly and terminates at the expected count. Add a breakpoint or watch window entry for IR0.
- High-byte zero check: After every low-byte extraction, verify that the high byte of the destination word is 0x00. Any non-zero value indicates a masking error that will corrupt downstream processing.
- Loopback test: Connect a wire from TX to RX on the serial port. Transmit a known pattern; verify the received pattern matches byte-for-byte.
Edge Cases: When Byte Access Matters Most
In most modern applications, the word-oriented model of Omron CJ-series PLCs is invisible to the programmer. Three edge cases make byte access relevant:
- Serial protocols with 8-bit data fields: Modbus RTU, ASCII protocols, and many RFID and barcode readers transmit 8-bit data. The TXD/RXD packing overhead is unavoidable. For Modbus RTU specifically, the CJ1W-SCU or CJ2M built-in serial port can be configured for protocol macro mode to handle byte-packing automatically, reducing programmer burden.
- Ethernet/IP explicit messaging with byte arrays: Some CIP (Common Industrial Protocol) assemblies use 8-bit SINT elements. The CJ2H-CPU6x-EIP implementation requires manual byte packing into INT (16-bit) arrays when transferring data to or from DM memory.
- String handling: Omron STRING data type in CX-Programmer is byte-addressable internally and tracks length in the first word. Conversion to or from DM words for storage or transmission requires length tracking plus per-byte masking as described in this reference.
For all three cases, the ANDW plus rotation or ANDW plus stack-pointer methods described earlier provide workable solutions. The choice depends on whether memory efficiency (packed, rotation method) or scan time (unpacked, index-pointer method) is the priority for the specific application.
FAQ
Can I address a single byte in an Omron CJ1 DM word directly?
No. The CJ1 and CJ2 instruction set does not support direct byte addressing in DM (or any other) memory. The smallest addressable unit is the bit, written as D0.00 through D0.15. To work with individual bytes, use the ANDW instruction with a hex mask (0x00FF for low byte, 0xFF00 for high byte) or a stack-pointer loop with index register IR0.
What is the difference between ANDW and the MVM instruction in Allen-Bradley?
ANDW in Omron performs a bitwise AND of two 16-bit words and stores the result in a destination word. MVM (Masked Move) in Allen-Bradley SLC 500 and MicroLogix performs a similar operation but can selectively pass or block bits in a single source word, with the mask and source combined via AND and the result ORed into the destination. Both serve the same purpose for byte isolation; the choice depends on which platform is in use.
How do I send a single ASCII character over RS-232 from a CJ1M-CPU12?
Use the TXD instruction with a DM word containing the ASCII character in the low byte and 0x00 in the high byte. For example, to send 'A' (0x41): MOV 16#0041 D2000, then TXD D2000 D2001 #0001. The receiving device will see the byte 0x41 regardless of the high-byte zero padding. Set the serial port to 8 data bits, no parity, 1 stop bit in the PLC Setup area or in CX-Programmer's Serial Communications dialog.
Does the CJ2 series change the memory architecture from CJ1?
No. The CJ2 series (CJ2H, CJ2M) maintains the same 16-bit word organization for DM, EM, CIO, WR, HR, and AR memory areas. The CJ2 adds more memory capacity, faster scan times, and new instructions (including function-block structured text) but the underlying word-based addressing is unchanged. Programs written for CJ1 typically transfer to CJ2 with little or no modification.
Is there a byte data type in CX-Programmer for CJ1 or CJ2?
CX-Programmer (CX-One version 4.0 or later) supports STRING data types that are byte-addressable internally and track length in the first word. However, when STRING data is moved to DM memory for storage or transmission, it is still stored as 16-bit words with the same packing considerations. For pure byte arrays in DM, the ANDW masking or stack-pointer techniques described in this reference are required.