[Introduction]
The I2C bus is widely used as a serial bus that connects EEPROM, display controllers, and A/D or D/A converters to microcontrollers. In many microcontroller products, it is relatively easy to use the I2C bus with an on-chip dedicated interface circuit, such as is the case with the 78K0/Kx2.
The following describes the programs used to control the I2C bus in single master mode when using ordinary ports in 78K0S devices that do not include an on-chip dedicated interface.
[Overview of I2C bus]
To perform communications, the I2C bus uses two signals: an open collector or open-drain output clock line signal (SCL) and a data line signal (SDA). Resistors are used to pull up these two signal lines, which are at high level when not in use. Devices that use the I2C bus include master devices that control communications and slave devices that are controlled by a master device. The following steps are followed when a master device uses the I2C bus.

Bus access is declared by issuing a start condition

First, an address is transmitted to specify the target slave device and to specify the communication direction

Communication starts when the specified slave device has responded

When a device receives data, it returns a response for each data unit (byte)

When the communication has ended, a stop condition is issued to release the bus.
For further description of the I2C bus, see the FAQ entitled
"Overview of I2C bus"at the NEC Electronics website. In this description, operations comply with "fast mode", which supports transfer rates up to 400 kbps.
[Target ports]
The I2C bus must be driven via an open-drain output. When only an ordinary CMOS output is available, some other port usage must be devised. Specifically, the port's output latch is fixed to zero and processing is performed to switch between input and output operations. In other words, the following processing is performed.
- 0 is set to the PM register → Output port → Low level
- 1 is set to the PM register → Input port → High level
With the above processing, the bus status can be read by outputting to the bus with output at high level.
[Main operations and processing of I2C bus]
The I2C bus's main operations are described below. Here, communication from the master device to a slave device is described as transmission and communication from a slave device to the master device is described as reception.
(1)Acknowledgment of bus status
In cases such as when the CPU must re-execute processing due to a reset that has been set while using the bus, the slave device (SDA) may pull the bus to low level. When this occurs, the master device cannot use the bus. Accordingly, processing must begin by checking whether the bus has been released.
(2)Bus release
Unless the bus is released, a dummy clock will be output to the SCL until the SDA is set to high level. Ordinarily, SDA is set to high level within nine clock cycles (data + ACK). If the actual program has set a wait for the slave device (which is possible but highly unlikely), it is advised to set SDA to high level as soon as 256 dummy clocks have been output at normal pulse width, instead of waiting indefinitely for the bus to be released. Once it is confirmed that SDA is at high level, a stop condition is issued to release the bus.
(If a slave device outputs high level, but the next data is at low level, the slave device may output the next data (at low level) by setting the SCL to low level. To avoid this, the SDA is set to low level before being set again to high level. When SDA is started, it is assumed that a start condition has been issued, and since a stop condition is issued later, the timing standard is met.)
(3)Issuance of start condition
Once the bus has been released, a start condition is issued, and bus access is reported to the slave device.
(4)Transmission of slave address
When an actual communication starts, a total of 8 bits of data is transmitted, including the slave device's address (7 bits) and the communication direction (1 bit). If the slave whose address matches the transmitted address does not return an ACK signal, it is assumed that there is no slave device corresponding to that address, so the communication is terminated and a stop condition is issued to end it. If an ACK signal is returned, the communication continues in the specified direction.
(5)Transmission of sub-address
Although the I2C bus can specify the target slave device by transmitting the slave address, the slave device may have its own internal address information. For example, an internal address is specified after transmitting the slave address, in order to specify the internal address in EEPROM. If the slave's internal address capacity does not exceed 256 bytes, this is a one-byte specification. The specified address is usually retained and is refreshed by read and write operations.
(6)Transmission of data
After transmitting a sub-address, data is transmitted and written to the specified sub-address. (In EEPROM, the actual write operation starts after receiving a stop condition.)
When data that includes the sub-address is transmitted, the slave device returns an ACK signal if it has successfully received the data. Once this ACK signal is returned, the next processing begins, but if it is not returned, the communication is terminated and a stop condition is issued to end it.
(7)Reception of data
When reception is specified as of the communication direction specifications, data reception is performed once the slave address has been transmitted. The master that receives the data returns an ACK signal in order to receive the next data. When the received data is the final data and no more data will be received, a NACK signal must be returned to indicate to the slave device that reception has been completed.
(8)Issuance of stop condition
To release the bus after a communication has ended, a stop condition is issued by setting the SDA to high level while the SCL is at high level.
(9)Issuance of restart condition
To change the data transfer direction while maintaining the bus mastership, a second start condition is issued. Immediately after this restart, the slave address is transmitted and the communication direction can then be specified again.
Generally, this method is used to specify an internal address and then read data from the slave device. Usually, the slave address is transmitted into the transmission direction first and the slave's internal address is specified as a sub-address. Next, a restart condition is issued, after which the reception direction is specified and the slave address is transmitted. This enables reading of data from the specified internal address in the slave device, after which data can be successively read from the specified address.
[Main bus statuses (reference)]
The main bus statuses are a described below.
| | Status | SCL | SDA |
| (1) | Bus not used | H | H |
| (2) | Data transition | L | Data is in transition |
| (3) | Data is stable | H | H/L |
| (4) | After issuance of start condition | H | L |
| (5) | Immediately after address transmission (9-clock wait mode) | L | ACK→H |
| (6) | 9-clock wait mode (standby for next transmission) | L | Next data |
| (7) | After address transmission (communications starts when reception is specified) | L | Receive data |
| (8) | After address transmission (communications starts when transmission is specified) | L | Transmit data |
| (9) | 8-clock wait mode | L | L=ACK/H=NACK |
| (10) | After issuance of stop condition (bus is not used) | H | H |
- The two signal lines (SCL and SDA) are at high level when the bus is not being used, as in steps (1) and (10), above.
- Basically, SDA changes when SCL is at low level and SDA is stable when SCL is at high level.
- After a start condition is issued (SDA is set to low level while SCL is at high level), SCL remains at high level and SDA is at low level. To transmit the slave device's address subsequently, set SCL to low level first.
- At step (5) or (6) above, which occurs after SCL has sent nine clock cycles during address transmission or data communications, setup for the next communication starts when SCL is at low level. If there is no next communication, SDA is set to high level. Otherwise, the start bit of the next data is output at step (7) or (8) above.
- After 8 bits of data has been transmitted and received at step (9), SCL is set to low level and then waits for an ACK signal response.
- At step (6) or (9), the slave device can delay communications by pulling SCL to low level. Accordingly, the master device must confirm that SCL is at high level before proceeding to the next processing.
[Sample program]
Although the basic control is performed by the assembler program, the control functions can be called from C programs. (The sample program shown below is only for reference.)
For this program, a 78K0S/KY1+ microcontroller operates using a high-speed internal oscillator, and P2.2 is used for SDA signals while P2.3 is used for SCL signals. When other bits in P2 register are manipulated, the output latches for P2.2 and P2.3 are overwritten. When other bits in P2 register are used as output ports, the output latch's data images are set up in internal RAM. To manipulate the port output, manipulate RAM data so that the result is written to the port.
Caution 1
When processing speed is demanded, programs are often coded in assembly language whereas main control functions are coded in C language. In such cases, subroutines coded in assembly language are called as functions from the C language program, however caution is required concerning the naming of the subroutines. In assembly language, a subroutine name must always be specified starting with "__" (two underscores). When called from a C language program, they are specified using only "_" (one underscore).
For example, a program that initializes the port to be used by the I2C bus might use the name "__setup_i2c_port" in its assembly code sections and might use "_setup_i2c_port();" when calling a C language program.
|
A byte variable (i2c_error) for the error status of execution results and a bit variable (acke) that specifies whether to return an ACK signal to the slave device are provided for internal RAM. A declaration example is shown below. These variables are all controlled by a specialized program.
comdata DSEG SADDR ; Saves variables to saddr area
i2c_error: DS 1 ; Error flag
BSEG ; Indicates bit variable areas
acke DBIT ; ACK control flag
(1)Port definitions
PMSW_SDA is the name used for the SDA signal. When this signal's value is 1, the pin is an input port, and PSW_SDA can be read to detect the status of the SDA signal. If there is no output from the slave device, the SDA signal is set to high level. When its value is "0", the pin is an output and the SDA signal is at low level since the output latch value is also 0.
PMSW_SCL is the name used for the SCL signal. When this signal's value is 1, the pin is an input port, and PSW_SCL can be read to detect the status of the SCL signal. Normally, SCL is set to high level by setting 1. Unless a wait is issued by the slave device, it is pulled to high level by an external pull-up resistor. When its value is 0, the pin is an output port and the SCL signal can be set to low level.
Definition example for assembler (definition of port to be used)
PMSW_SDA equ PM2.2 ; Used for controlling SDA signal
PSW_SDA equ P2.2 ; Used for reading SDA signal
PSW_I2C EQU P2 ; Port 2 is used
PMSW_SCL equ PM2.3 ; Used for controlling SCL signal
PSW_SCL equ P2.3 ; Used for reading SCL signal
The following macros are defined to make the meaning of actual port operations (mode register operations) easier to understand.
Port operation definition
_scl_hi macro ; Indicates "_scl_hi" macro definition
set1 PMSW_SCL ; Sets up PM2.3 for input
endm ; Indicates end of macro definition
_scl_lo macro
clr1 PMSW_SCL ; Clears PM2.3 and outputs at low level to SCL
endm
_sda_hi macro
set1 PMSW_SDA ; Sets PM2.2 for input
endm
_sda_lo macro
clr1 PMSW_SDA ; Clears PM2.2 and outputs at low level to SDA
Note 1
Macro functions are used when creating programs to code typical processing more efficiently and to make the program easier to understand. In this case, the processing corresponds to the bit manipulation instructions for port mode register. In this case, macro functions are used to make the program easier to understand rather than to make coding of typical processing more efficient. By writing "_scl_lo" instead of "CLR1 PMSW_SCL", it becomes easier to intuitively grasp the type of processing to be performed.
|
(2) Port initialization (__setup_i2c_port)
The output latch of the port to be used is set to 0 (0 is written in byte units to the port). The port mode is then set to input mode. In this program, a public declaration using names that start with "__" is used to call from parts of the C language code.
public __setup_i2c_port ; Public declaration enabling use
; from external source
__setup_i2c_port:
MOV PSW_I2C,#0 ; Public declaration enabling use
; from external source
; Data is fixed to zero during use
_scl_hi ; PM is input
_sda_hi
RET
(3) Bus availability check (__i2c_busy)
This checks for bus availability. The result is returned using a carry flag. When the bus is being used locally or when SCL or SDA is at low level, the bus is judged to be busy and the carry flag is set (return value is "true"). If the bus has been released, the carry flag is cleared (return value is "false"). There is no processing to change the port status. Here, a special name (i2c_busy) is added to make it easier to reference in assembly language.
;bit _i2c_busy(void)
public __i2c_busy
__i2c_busy:
i2c_busy:
SET1 CY ; Set busy flag
BF PSW_SDA,$busbusy ; Check SDA line
BF PSW_SCL,$busbusy ; Check SCL line
CLR1 CY ; Flag is reset when bus is released
busbusy:
RET
Note 2
To check single-bit port status in the 78K0S, conditional branch instructions (BF or BT instruction) such as shown in the above sample program are used. When using a BF instruction, a branch occurs when the single-bit port value is 0 and does not occur when it is 1. In the above sample program, when both PSW_SDA and PSW_SCL are 1, neither of the two conditional branch instructions execute a branch and CY (carry flag) is cleared.
|
(4) Bus release (__free_i2c_bus)

If a check of the bus status indicates that the bus has not been released, dummy clocks are sent while waiting for bus availability. If the bus cannot be released by 256 clock pulses, the carry flag is cleared (return value is "false") to return an error.
A stop condition is issued once the SDA and SCL signals are at high level.
Caution 2
Under this status, if SCL is set to low level, the slave device may output at low level for the next data (when the slave device's output data is 1, but the next data is 0). In this case, when SCL is at high level, set SDA to low level first and then back to high level again . SDA's fall to low level is interpreted as issuance of a start condition, but a stop condition is later issued by rise to high level, so it is ignored.
|

After a stop condition has been issued to release the bus, the carry flag is set (return value is "true") to return.
For the dummy clock transmission that is used for this, the operating clock time is held at 12 clock cycles to preserve the SCL's low level width as 1.3µs. The high level width is not necessarily maintained, since it includes overheads such as subroutine calls. If the slave device has set a wait, low level is maintained in the bus even when the master device is set to high level. If this status continues, the I2C bus cannot be used at all. Consequently, there is no waiting for SCL to go to high level. With a margin, processing is terminated after 256 times.
;bit _free_i2c_bus(void)
public __free_i2c_bus
__free_i2c_bus:
free_i2c_bus:
_sda_hi ; SDA is set to high level as a precaution
_scl_hi ; SCL is set to high level as a precaution
CALL !i2c_busy ; Check bus status
NOT1 CY
BC $free_exit1 ; Exit when bus has been released
MOV A,#0 ; Set limit of 256 times
dumyclkloop:
CALL !scl_pulse ; Dummy clock is output to SCL
CALL !i2c_busy ; Bus status check
NOT1 CY
BC $free_exit1 ; Exit when bus has been released
DEC A ; Has limit been exceeded?
BNZ $dumyclkloop ; Continue if within limit
RET ; Set carry flag and return if error occurs
free_exit1:
PUSH PSW ; Save flag as a precaution
CALL !I2C_STPR ; Issue stop condition
POP PSW
RET
A dummy clock that meets the I2C bus standard is generated.
scl_pulse:
_scl_lo ; 6:SCL is set to low level
NOP ; 2:To secure the low-level width of 1.3 µs,
NOP ; 2:wait at least 11 clock cycles
NOP ; 2:before activation.
_scl_hi ; 6:SCL is set to high level.
RET ; 6:High-level period is enough.
(5) Issuance of start condition (__I2C_STR)
To prepare for issuance of a start condition, first set SDA then SCL to high level. (If SCL is already at high level, this is the same as a stop condition. For a restart, SCL must be at low level.)
Afterward, the BF instruction confirms that SCL is at high level, then SDA is set low to issue a start condition. (This is inserted in order to wait for the slave device's wait mode to be canceled at the ninth clock. In almost all cases, this is considered unnecessary. To delete the BF instruction, replace it with two NOP instructions.)
Note 3
The I2C bus has a communication pause function that operates when the slave device pulls SCL to low level. Consequently, after the master device has set SCL to high level, a check is performed to confirm that SCL is actually at high level. "BF PSW_SCL,$$" is then used. If the SCL line is at low level (as read by PSW_SCL), this command branches itself as a repeated command. This enables progress to the next processing when SCL is set to high level.
|
public __I2C_STR
__I2C_STR:
I2C_STR:
_sda_hi ; 6:SDA is set high for preparation
NOP ; 2:
_scl_hi ; 6:SCL is set high
BF PSW_SCL,$$ ;10:SCL's rise is detected
_sda_lo ; 6:Start condition is issued
RET ; 6:
(6) Issuance of stop condition (__I2C_STPR)
Since setting SCL to low level first may cause the next transfer to start, processing is performed when SCL is already at low level. If SCL is at high level, setting SDA to low level is the same as issuing a stop condition. Nevertheless, it should not be considered inappropriate to subsequently issue a stop condition.
In this case as well, the BF instruction confirms that SCL is at high level before the next processing is performed. This is done for the same reasons as when issuing a start condition, as described above.
public __I2C_STPR
__I2C_STPR:
I2C_STPR:
_sda_lo ; 6:SDA is set to low level. Even if this is
NOP ; 2:interpreted as a start condition, a stop
NOP ; 2:condition can be issued immediately.
_scl_hi ; 6:SCL is set to high level as a precaution.
BF PSW_SCL,$$ ;10:Rise of SCL is detected
_sda_hi ; 6:Stop condition is issued
NOP ; 2:
NOP ; 2:
RET ; 6:
(7) Output 8-bit data to I2C bus (__put_i2c)
This routine outputs to the I2C bus 8-bit data that has been passed via the X register that is used to receive and pass single-byte arguments during C language's normal mode. When SCL is at low level, output proceeds starting from the MSB, then an ACK response check is performed. If there is an ACK response, the carry flag is set (return value is "true") and returned.
If there is no ACK response, the carry flag is cleared (return value is "false") and returned.
When processing has ended, SCL is at low level (9-clock wait mode).
Note 4
When a transmission starts (9-clock wait mode from the previous communication), a wait for wait cancellation is set, along with SCL rise wait processing to confirm whether SCL has actually gone to high level. (This processing is probably not necessary but is input to all bits as a precaution. If it is deemed unnecessary because the parts where waits are not inserted have already been checked, then it should be deleted and replaced with two NOP instructions.)
|
[Program flow]
The program flow is illustrated as follows.
[Sample program]
;bit _put_i2c(u8 data);
public __put_i2c
__put_i2c:
put_i2c:
PUSH BC ; Saved for use with loop counter
MOV B,#8
MOV A,X ; Input data (argument) is 8 bits
;
;
OUTLOOP:
_scl_lo ; 6:Data output setup when SCL goes low
NOP ; 2:For fall time adjustment
ROL A,1 ; 2:Shift out from MSB to CY
BNC $OUTLO ; 6:Branch when data is 0
_sda_hi ; 6:Output 1
BR $CLKHI ; 6:SCL is set high
OUTLO: _sda_lo ; 6:0 is output
NOP ; 2:Secure setup time
CLKHI:
_scl_hi ; 6:
BF PSW_SCL,$$ ;10:SCL's rising edge is detected (as a precaution)
DBNZ B,$OUTLOOP ; 6:
POP BC ; 6:
_scl_lo ; 6:Set SCL low at eighth clock
NOP ; 2:
_sda_hi ; 6:SDA is switched to input
NOP ; 2:
NOP ; 2:
NOP ; 2:
_scl_hi ; 6:Set SCL high at ninth clock
;
; Wait for wait cancellation
;
SET1 CY ; 2:
BF PSW_SCL,$$ ; Wait for detection of SCL's rise
BF PSW_SDA,$ACKOK ;10:Obtain/ACK signal
NOT1 CY ; 2:
ACKOK:
_scl_lo ; 6:Set SCL low (9th clock)
NOP
RET
(8) 8-bit data is output to the I2C bus. The bus is released when an error occurs (__put_i2c2)
Data is output by the routine shown in (7) above, and if an error occurs a stop condition is issued and returned. The I/O conditions are the same as in (7).
;static bit _put_i2c2(u8 data)
public __put_i2c2
__put_i2c2:
CALL !put_i2c ; Output 1 byte
BNC $PUTERROR ; Go to termination processing if error occurs
RET ; End if normal
PUTERROR:
CALL !I2C_STPR ; Issue stop condition
CLR1 CY ; Set as false (as a precaution)
RET
(9) 8-bit data is input from I2C bus (__get_i2c)
8-bit data is received from the slave device selected for the I2C bus, and the received data is saved to the C register and returned. Data from the slave is read starting from the MSB when SCL is at high level. If the acke bit = 1 after reception completion, an ACK response is sent to notify the slave device for following data. If the acke bit = 0, a NACK response is sent to notify the slave device that the last data has been received.
When processing has ended, SCL is at low level (9-clock wait mode).
[Program flow]
The program flow is illustrated as follows.
[Sample program]
;u8 _get_i2c(void);
public __get_i2c
__get_i2c:
MOV A,#11111110b ; Shift and end when CY = 0
INLOOP:
NOP ; 2:
SET1 CY ; 2:Set 1 as initial value
_scl_hi ; 6:SCL goes high
BF PSW_SCL,$$ ; SCL rise is detected
BT PSW_SDA,$DATAIS1 ;10:
NOT1 CY ; 2:Invert carry when data is 0
DATAIS1:
ROLC A,1 ; 2:Shift in read result to A register
_scl_lo ; 6:SCL goes low
BC $INLOOP ; 6:8-bit loop
;******************************************************
;* ACK response control *
;******************************************************
BF _acke,$ACKEND ;10:Check for ACK response
_sda_lo ; 6:Return ACK
ACKEND:
_scl_hi ; 6:Rise of 9th clock
;
; This is the response when the slave device has an 8-clock wait
; while waiting for the SCL signal to go to high level.
; Normally not required?
;
BF PSW_SCL,$$ ; Detection of SCL's rise (as a precaution)
NOP ; 2:
_scl_lo ; 6:Falling edge of ninth clock
MOV C,A ; 4:return (u8)
_sda_hi ; 6:SDA is set to input mode when completed
RET
Note 5
Here, an A register is used for loop counting. The capture (shift-in from right) of receive data (bits) to the A register is combined with this loop counting operation. When 11111110b is set to the A register as the initial value, it changes with each shift-in to 1111110xb → 111110xxb → and 11110xxxb. When the seventh time is completed, the setting is 0xxxxxxxb, and the shift result so far causes the carry flag to be set. When the 8th shift is performed, the last bit of the initial value 0 is sent to be carried. Accordingly, when the carry value is 0, it indicates that the 8th time is completed.
|
(10) Clear error flag (__clear_i2c_error)
This processing simply clears internal variables. The error flag status is the return value.
;static u8 _clear_i2c_error(void)
public __clear_i2c_error
__clear_i2c_error:
clear_i2c_error:
MOV i2c_error,#I2C_NO_ERROR ;
MOV C,#I2C_NO_ERROR
RET
(11) Set error flag (__set_i2c_error)
This sets the error status to an internal variable. Errors are defined as follows.
I2C_NO_ERROR = 0x00:No error
I2C_BUS_NOT_FREE = 0x10:Bus has not been released
I2C_ADDR_NACK = 0x11:Slave device has not responded to address
I2C_RADDR_NACK = 0x12:Slave device has not responded to read address
I2C_REG_ADDR_NACK = 0x13:Unexpected NACK was returned from slave device (register)
I2C_DATA_NACK = 0x14:Unexpected NACK was returned from slave device (data)
;static u8 _set_i2c_error(u8 error)
public __set_i2c_error
__set_i2c_error:
set_i2c_error:
XCH A,X ; Capture error data
MOV i2c_error,A ; Set error flag
MOV C,A ; Set return value
XCH A,X
RET
(12) Read error flag (__get_i2c_error)
This reads the contents of error flags. The errors are defined in (11) above.
;static u8 _get_i2c_error(void)
public __get_i2c_error
__get_i2c_error:
get_i2c_error:
PUSH AX
MOV A,i2c_error ; Read error flag
MOV C,A ; Set return value
POP AX
RET
(13) Set ACK response enable flag (__ACK_EN)
This sets enable status for ACK responses issued when the master device receives data. This status should usually be set to "enable" and set to "disable" when receiving the last data.
;void _ACK_EN(void)
public __ACK_EN
__ACK_EN:
ACK_EN:
SET1 acke ; Set ACK response flag
RET
(14) Set ACK response disable flag (__ACK_DS)
This is used to set a NACK response when the master device receives the last data.
;void _ACK_DS(void)
public __ACK_DS
__ACK_DS:
ACK_DS:
CLR1 acke ; Clear ACK response flag
RET
(15) Master transmit processing (__write_i2c_block)
The I2C bus is used to write to a 256-byte EEPROM. To accommodate use of this subroutine from C language (normal mode), the required parameters are passed using the same format as in C language. The passed parameters have the following format.
| First argument:X register | | :Address of I2C bus's slave device |
| Second argument:First stack layer | | :Internal address to be accessed by slave device |
| Third argument:Second stack layer | | :Address of buffer where data is stored |
| Fourth argument:Third stack layer | | :Byte count of write data |
These parameters are pushed onto the stack in two-byte units, from the fourth argument to the second argument in that order, and then this subroutine is called.
This subroutine writes the number of bytes of data specified by the fourth argument from the buffer memory specified by the third argument to the internal addresses that start at the address specified by the second argument in the EEPROM at the slave device address specified by the first argument. Once this write operation is completed, the bus is released and the error flag is cleared and returned. Since the return value that is stored in the C register is the same as the error flag value, errors can be confirmed by checking the returned value. (The errors are described above in "(11) Set error flag")
[Program flow]
The program flow is illustrated as follows.
The assembly language code's __put_i2c2 function is used for transmit processing in byte units, and if an error occurs the bus is released and an error corresponding to the status where an error occurred is returned. If there are no errors, the bus is released and no errors are returned. (EEPROM detects a stop condition and writes the transmitted data to an internal cell.)
An example of a C language program that performs the same processing as this sample program is shown in section (1) under "Program Use Examples" below. Although the sequence of processing differs slightly, the processing itself is the same. The same function call methods and return values are used.
[Sample program]
;**************************************************************
;* *
;* Block data transfer to I2C bus *
;* Targets are devices with sub-addresses *
;* of 256 bytes or less *
;* ADR(W), SUB_ADR, DATA... *
;**************************************************************
;* [i] : u8 i2c_adr ... Address of I2C slave device *
;* [i] : u8 reg_adr ... Address within slave device *
;* [i] : u8 *data ... Address of write data *
;* [i] : u8 size ... Byte count (0x00 = 256) *
;* [r] : u8 ... error code *
;* *
;* The actual arguments are pushed onto the stack *
;* in 16-bit units, so the required arguments are gotten by *
;* moving the SP value to HL register and specifying the *
;* relative address based on HL. *
;* *
;**************************************************************
;u8 _write_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)
public __write_i2c_block
__write_i2c_block:
PUSH HL ; Save register
PUSH AX
;
; This subroutine is called via a function call from C (normal mode).
; The stack data at this stage is as follows.
;
; sp : X register (first argument) : Slave address
; sp+1 : A register : Not used (only for register saving)
; sp+2 : L register : Not used (only for register saving)
; sp+3 : H register : Not used (only for register saving)
; sp+4 : Lower PC : Lower return address
; sp+5 : Higher PC : Higher return address
; sp+6 : Second argument : Address in slave device
; sp+7 : : Not used (only for register saving)
; sp+8 : Lower values of third argument : Write data save address
; sp+9 : Higher values of third argument
; sp+10: Fourth argument : Write data count
MOVW AX,SP ; Prepare to pop the second and subsequent arguments
MOVW HL,AX
MOV X,#I2C_BUS_NOT_FREE
CALL !set_i2c_error ; Use dummy to set error
CALL !free_i2c_bus ; Can bus be released?
BNC $EXITSUB ; Returned when bus cannot be released
CALL !I2C_STR ; Issue start condition
MOV X,#I2C_ADDR_NACK
CALL !set_i2c_error ; Use dummy to set error
MOV A,[HL] ; Pop slave address
AND A,#11111110b ; Set direction flag to transmit
MOV X,A
CALL !put_i2c2 ; Transmit slave address in transmit direction
BNC $EXITSUB ; Return with error if no response from slave device
MOV X,#I2C_REG_ADDR_NACK ;
CALL !set_i2c_error ; Set dummy error
MOV A,[HL+6] ; Pop second argument from stack
MOV X,A
CALL !put_i2c2 ; Transmit slave device's internal address
BNC $EXITSUB ; Return with error if no response from slave device
MOV A,[HL+10] ; Pop fourth argument from stack
MOV B,A ; Set data count to B register
MOV A,[HL +8] ; Pop third argument from stack
MOV X,A ; Set third argument (write data save address)
MOV A,[HL+9] ; to
MOVW HL,AX ; HL register
MOV X,#I2C_DATA_NACK ; Set "no ACK response to data" to flag
CALL !set_i2c_error ; as dummy setting
WBLOOP:
MOV A,[HL] ; Read write data from buffer
MOV X,A
CALL !put_i2c2 ; Write data
BNC $EXITSUB ; Returned if error occurs
INCW HL ; Refresh data address
DBNZ B,$WBLOOP ; Repeat in data count units
CALL !I2C_STPR ; Release bus
CALL !clear_i2c_error ; Clear error flag
EXITSUB:
MOV B,#0 ; Clear higher return value (unnecessary?)
POP AX
POP HL
RET
(16) Master device receive processing (__read_i2c_block)
Using the I2C bus, data is read from a 256-byte EEPROM.
The number of bytes specified by the fourth argument is read to the buffer specified by the third argument from the addresses starting at the internal address specified by the second argument in the slave device specified by the first argument.
The assembly language code's __put_i2c2 function is used for transmit processing for the slave address or slave internal address, and if an error occurs the bus is released and an error corresponding to the status where an error occurred is returned. If there are no errors during this processing, the bus is released and no errors are returned.
[Program flow]
The program flow is illustrated as follows.
[Sample program]
;****************************************************************
;* *
;* Reception of block data from I2C device *
;* Targets are devices with sub-addresses of 256 bytes or less *
;* ADR(W), SUB_ADR, DATA... *
;****************************************************************
;* [i] : u8 i2c_adr ... Address of I2C slave device *
;* [i] : u8 reg_adr ... Address within slave device *
;* [i] : u8 *data ... Address of write data *
;* [i] : u8 size ... Byte count (0x00 = 256) *
;* [r] : u8 ... error code *
;* *
;* The actual arguments are pushed onto the stack in 16-bit *
;* units, so the required arguments are gotten by moving the *
;* SP value to HL register and specifying the relative *
;* address based on HL. About the relative address, refer to *
;* [Sample program] of "(15) Master transmit processing". *
;****************************************************************
;u8 _read_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)
public __read_i2c_block
__read_i2c_block:
PUSH HL
PUSH AX
MOVW AX,SP ; Prepare to pop the second and subsequent
; arguments
MOVW HL,AX ; Set pointer to HL register
MOV X,#I2C_BUS_NOT_FREE
CALL !set_i2c_error ; Use dummy to set error
CALL !free_i2c_bus ; Can bus be released?
BNC $EXITSUBR ; Returned when bus cannot be released
CALL !I2C_STR ; Issue start condition
MOV X,#I2C_ADDR_NACK
CALL !set_i2c_error ; Use dummy to set error
MOV A,[HL] ; Pop slave address
AND A,#11111110b ; Set direction flag to transmit
MOV X,A
CALL !put_i2c2 ; Transmit slave address in transmit direction
BNC $EXITSUBR ; Return with error if no response from slave device
MOV X,#I2C_REG_ADDR_NACK
CALL !set_i2c_error ; Use dummy to set error
MOV A,[HL+6] ; Pop second argument from stack
MOV X,A
CALL !put_i2c2 ; Transmit slave device's internal address
BNC $EXITSUBR ; Return with error if no response from slave device
CALL !I2C_STR ; Issue restart condition
MOV X,#I2C_RADDR_NACK
CALL !set_i2c_error ; Use dummy to set error
MOV A,[HL] ; Pop slave address
OR A,#00000001b ; Set direction flag to transmit
MOV X,A
CALL !put_i2c2 ; Transmit slave address in receive direction
BNC $EXITSUBR ; Return with error if no response from slave device
SET1 acke ; Set ACK response
MOV A,[HL+10] ; Pop fourth argument from stack
MOV B,A ; Set data count to B register
MOV A,[HL+8] ; Pop third argument from stack
MOV X,A ; Third argument (write data save address)
MOV A,[HL+9] ; is set
MOVW HL,AX ; to HL register
BRLOOP:
MOV A,#1 ; Check whether this is the last data
CMP A,B
BNZ $BRNEXT ; If it is the last data (1 byte remaining),
CLR1 acke ; set a NACK response.
BRNEXT:
CALL !get_i2c ; Read data
MOV A,C ;
MOV [hl],a ; Write read data to buffer
INCW HL ; Refresh pointer
DBNZ B,$BRLOOP ; Repeat in data count units
CALL !I2C_STPR ; Release bus
CALL !clear_i2c_error ; Clear error flag
EXITSUBR:
MOV B,#00H ; Any extra?
POP AX
POP HL
RET
[Program use example]
As an example of using the assembly language-based programming described above, the following is an example of function calls set in a C language program.
The functions shown in (1) and (2) below perform the same processing as the assembly language programs shown above in (15) and (16). In these examples, basic control routines that have been coded in assembly language are used to control a 256-byte EEPROM via the I2C bus. In addition, (3) is an example of using assembly language programs such as in (15) and (16), and programs using a function such as in (1) and (2) below.
When using the examples described below, a declaration, such as one that declares "unsigned char" as "u8", must be entered in order to reference the assembly language programs from an external source.
#pragma SFR
#pragma EI
#pragma DI
#pragma NOP
typedef unsigned char u8, uint8, /* same as "byte" */
*u8Ptr, *uint8Ptr; /* same as "bytePtr" */
/* Error flag manipulation-related processing functions */
extern u8 _set_i2c_error(u8 error);
extern u8 _get_i2c_error(void);
extern u8 _clear_i2c_error(void);
/* Error flag status definitions */
enum{
I2C_NO_ERROR = 0x00, // No errors (0)
I2C_BUS_NOT_FREE = 0x10, // Bus has not been released
I2C_ADDR_NACK = 0x11, // No response to address from slave device
I2C_RADDR_NACK = 0x12, // No response to READ address from slave device
I2C_REG_ADDR_NACK = 0x13, // Unexpected NACK was returned (register)
I2C_DATA_NACK = 0x14 // Unexpected NACK was returned (data)
};
/* ACK response control-related processing functions */
extern void _ACK_EN(void);
extern void _ACK_DS(void);
extern void _setup_i2c_port(void);
extern bit _i2c_busy(void);
extern bit _free_i2c_bus(void);
/* I2C bus initialization-related processing functions */
extern void _I2C_STR(void);
extern void _I2C_STPR(void);
/* I2C bus byte transfer basic functions */
extern bit _put_i2c(u8 data);
extern bit _put_i2c2(u8 data);
extern u8 _get_i2c(void);
/* I2C bus block transfer processing functions */
extern u8 _write_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size);
extern u8 _read_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size);
Caution 3
These programs cause the output from an 8 MHz internal oscillator to be operated as a CPU clock. Accordingly, when the 78K0S/KY1+'s POC is cancelled, operation at 8 MHz is no longer possible, until the power supply voltage has reached at least 4V. Therefore, the following processing must be described in the hdwinit function to perform an initialization.
LVIS = 0x00; /* select VLI=4V */
LVIM = 0b10000000; /* Start VLI detection*/
PCC = 0x00; /* CPU clock is fx/4 */
_clear_i2c_error(); /* I2C flag clear */
_setup_i2c_port(); /* Initialize I2C port*/
/*
wait for 0.17ms(=0.2ms-(32+30)*52/1000)
*/
for (work1 = 0; work1 < 10; work1++){
NOP();
}
WDTM = 0b01110000; /* Stop WDT */
LSRSTOP = 1; /* Stop Low Speed OSC */
while ( LVIF ){
NOP();
} /* wait VDD > 4V */
PPCC = 0; /* CPU clock is fx=fR */
IF0 = 0x00; /* Clear interrupt */
|
Note 6
Here, option bytes must be specified for use in stopping WDT or a low-speed internal oscillator and for using the I2C bus with the X1 and X2 pins as ports. To do this, an assembly language definition file such as the following must be created, which then must be linked.
@@OPTB CSEG AT 0080H
DB 10011100b
DB 11111111b
END
|
(1) Master device's transmission processing (u8 write_i2c_block (u8 12c_adr, u8 reg_adr, u8 *data, u8 size))
This function writes to a 256-byte EEPROM via the I2C bus.
It writes the number of bytes specified by the fourth argument of the buffer contents specified by the third argument to addresses starting at the internal address specified by the second argument in the slave device specified by the first argument.
The assembly language code's __put_i2c2 function is used for transmit processing in byte units, and if an error occurs the bus is released and an error corresponding to the status where an error occurred is returned.
If there are no errors during this processing, the bus is released and no errors are returned. The processing steps are listed below.
- First, whether the I2C bus has been released is checked, and the bus is released if necessary.
- If the bus cannot be released, an error is returned.
- A start condition is issued and bus access starts.
- An EEPROM address is specified and is then sent in the transmit direction.
- Next, the address to be written within the EEPROM is transmitted.
- Subsequently, the specified number of data bytes is transmitted.
- When transmission is completed, a stop condition is issued. (EEPROM detects the stop condition and writes the transmitted data to an internal cell.)
u8 write_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)
{
i2c_adr &= 0xfe;
if (!_free_i2c_bus()) // Release bus
return _set_i2c_error(I2C_BUS_NOT_FREE); // If bus is not released,
// set an error flag and return.
_I2C_STR(); // Issue start condition
if (!_put_i2c2(i2c_adr)) // Transmit I2C address
return _set_i2c_error(I2C_ADDR_NACK); // If there is no response from
// the target device,
// set an error flag and return.
if (!_put_i2c2(reg_adr)) // Transmit register address
return _set_i2c_error(I2C_REG_ADDR_NACK); // If a NACK occurs for
// the sub-address,
// set an error flag and return.
do{
if (!_put_i2c2(*data++)) // Transmit data
return _set_i2c_error(I2C_DATA_NACK); // NACK occurred when
// transmitting data
}while(--size);
_I2C_STPR(); // Release bus after a normal end
return _clear_i2c_error(); // Clear error flag and return
}
(2) Master device's reception processing (u8 read_i2c_block (u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)
Using the I2C bus, data is read from a 256-byte EEPROM.
The number of bytes specified by the fourth argument is read to the buffer specified by the third argument from the addresses starting at the internal address specified by the second argument in the slave device specified by the first argument.
The assembly language code's __put_i2c2 function is used for transmit processing for the slave address or slave internal address, and if an error occurs the bus is released and an error corresponding to the status where an error occurred is returned. If no errors occur during this processing, the bus is released and no errors are returned. The communication steps are as follows.
- First, whether the I2C bus has been released is checked, and bus is released if necessary.
- If the bus cannot be released, an error is returned.
- A start condition is issued and bus access starts.
- An EEPROM address is specified and is then sent in the transmit direction.
- Next, the address to be read within the EEPROM is transmitted.
- A restart is executed.
- An EEPROM address is specified and is then sent in the receive direction.
- Data is read from the specified address.
- When reading, if the current data is not the last data, acke is set to send an ACK response and the assembler's __get_i2c function is called.
- For the last data, acke is cleared and a NACK response is set, then the __get_i2c function is called.
u8 read_i2c_block(u8 i2c_adr, u8 reg_adr, u8 *data, u8 size)
{
u8 d;
i2c_adr &= 0xfe;
if (!_free_i2c_bus())
return _set_i2c_error(I2C_BUS_NOT_FREE);
_I2C_STR(); // Issue start condition
if (!_put_i2c2(i2c_adr)) // Transmit I2C address
return _set_i2c_error(I2C_ADDR_NACK); // If there is no response from
// the target device,
// set an error flag and return.
if (!_put_i2c2(reg_adr)) // Transmit register address
return _set_i2c_error(I2C_REG_ADDR_NACK); // If a NACK occurs for
// the sub-address,
// set an error flag and return.
_I2C_STR(); // Restarts
if (!_put_i2c2(i2c_adr | 0x01)) // Transmit I2C address (READ)
return _set_i2c_error(I2C_RADDR_NACK); // If there is no response from
// the target device,
// set an error flag and return.
_ACK_EN(); // ACK is enabled
do{
if (size == 1) // Last byte?
_ACK_DS(); // If it is the last byte received
// from the slave device, a NACK is set.
d = _get_i2c(); // Read data
*data++ = d; // Set read data to buffer
}while(--size);
_I2C_STPR(); // Release bus
return _clear_i2c_error(); // Clear error flag and return
}
(3) EEPROM access example
In this use example, 32 bytes are set up for reception and 16 bytes are set up for transmission, and assembly language programs (15) and (16) are used, along with "(1) Master device's transmission processing"
and "(2) Master device's reception processing" from above.
u8 eep_read_buf[32]; // read buffer
u8 eep_write_buf[16]; // write buffer
u8 result; // for result flag
#define ADR_EEP 0xa0 // slave address
// 1010xxxyB
// ||||||||
// |||||||+-- R/W#
// |||||++--- P1/P0 (fixed 00)
// ||||+----- A2 (fixed 0)
// ++++------ I2C Addr (fix)
- An assembly code function is used to write 16 bytes of data from the buffer to addresses 00 to 0F in EEPROM.
result = _write_i2c_block(ADR_EEP, 0x00, eep_write_buf, 16);
- An assembly code function is used to write the data at addresses 00 to 1F in EEPROM to a buffer.
result = _read_i2c_block(ADR_EEP, 0x00, eep_read_buf, 32);
- A C language function is used to write 16 bytes of data from the buffer to addresses 00 to 0F in EEPROM.
result = write_i2c_block(ADR_EEP, 0x00, eep_write_buf, 16);
- A C language function is used to write the data at addresses 00 to 1F in EEPROM to a buffer.
result = read_i2c_block(ADR_EEP, 0x00, eep_read_buf, 32);