Skip to content

Commit

Permalink
Update of i2c hardware adding a timeout feature
Browse files Browse the repository at this point in the history
Preventing a hangup in case something goes wrong on the i2c bus.
  • Loading branch information
RobJansen62 committed Feb 24, 2024
1 parent 62e67eb commit 1f2a0ce
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 28 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Expand Up @@ -39,6 +39,7 @@ networking:
peripherals:
- Update of adc.jal to support newer PICs (alias added for ADCON0_GONDONE).
- Update of stopwatch.jal and stopwatch2.jal to support newer PICs
- Update of i2c_hardware.jal supporting a timeout to prevent hangup.

projects:
-
Expand Down Expand Up @@ -89,6 +90,7 @@ samples:
16f18175_blink_hs.jal, 16f18175_blink_intosc.jal
- Added 16f18126_blink_hs.jal, 16f18126_blink_intosc.jal
16f18146_blink_hs.jal, 16f18146_blink_intosc.jal
- Added 16f1823_i2c_hw_eeprom_serial.jal to demonstrate i2c timeout functionality

documentation:
-
Expand Down
1 change: 1 addition & 0 deletions TORELEASE
Expand Up @@ -1541,6 +1541,7 @@ sample/16f1823_bluetooth_hc06_slave.jal
sample/16f1823_data_eeprom.jal
sample/16f1823_dfplayer.jal
sample/16f1823_digital_potentiometer_x9c10x.jal
sample/16f1823_i2c_hw_eeprom_serial.jal
sample/16f1823_nrf24l01.jal
sample/16f1823_nrf905.jal
sample/16f1823_serial_hardware.jal
Expand Down
138 changes: 110 additions & 28 deletions include/peripheral/i2c/i2c_hardware.jal
@@ -1,7 +1,7 @@
-- Title: i2c_hardware
-- Author: Stef Mientki, Copyright (C) 2005..2019, all rights reserved.
-- Author: Stef Mientki, Copyright (C) 2005..2024, all rights reserved.
-- Adapted-by: Joep Suijs, Albert Faber, Rob Jansen
-- Compiler: >=2.5r2
-- Compiler: 2.5r8
--
-- This file is part of jallib (https://github.com/jallib/jallib)
-- Released under the ZLIB license (http://www.opensource.org/licenses/zlib-license.html)
Expand All @@ -17,8 +17,13 @@
-- const word _i2c_bus_speed = 1 -- 100kHz
-- const bit _i2c_level = true -- i2c levels (not SMB)
--
-- TODO:
-- The use can define a timeout as to prevent a hangup in case something goes wrong. The
-- timing is not exact but a reference of 1 us is used but since the loop time increases
-- it this time should be seen as the minimum wait time before it expires. This is an
-- optional setting:
-- const word I2C_HARDWARE_TIMEOUT = 10_000 -- Timeout is al least 10 ms.
--

-- Aliases to support other PICs.
if (defined(PIE3_SSP1IE) == TRUE) then -- PIE3/PIR3
alias PIR1_SSPIF is PIR3_SSP1IF
Expand Down Expand Up @@ -69,86 +74,163 @@ end procedure


-- -----------------------------------------------------------------------------
-- i2c_start - Sends Start bit and waits untill finished
-- i2c_start - Sends Start bit and waits until finished or timeout (if defined)
-- -----------------------------------------------------------------------------
procedure i2c_start() is

SSPCON2_SEN = high
while SSPCON2_SEN == high loop end loop
if defined(I2C_HARDWARE_TIMEOUT) then
var word timeout_counter = 0
while (SSPCON2_SEN == high) & (timeout_counter != I2C_HARDWARE_TIMEOUT) loop
timeout_counter = timeout_counter + 1
_usec_delay(1)
end loop
else
-- Without timeout.
while SSPCON2_SEN == high loop end loop
end if

end procedure
-- -----------------------------------------------------------------------------

-- -----------------------------------------------------------------------------
-- i2c_restart - Sends Restart bit and waits untill finished
-- i2c_restart - Sends Restart bit and waits until finished or timeout (if defined)
-- -----------------------------------------------------------------------------
procedure i2c_restart() is

SSPCON2_RSEN = high
while SSPCON2_RSEN == high loop end loop
if defined(I2C_HARDWARE_TIMEOUT) then
var word timeout_counter = 0
while (SSPCON2_RSEN == high) & (timeout_counter != I2C_HARDWARE_TIMEOUT) loop
timeout_counter = timeout_counter + 1
_usec_delay(1)
end loop
else
-- Without timeout.
while SSPCON2_RSEN == high loop end loop
end if

end procedure
-- -----------------------------------------------------------------------------

-- -----------------------------------------------------------------------------
-- i2c_stop - Sends Stop bit and waits until finished
-- i2c_stop - Sends Stop bit and waits until finished or timeout (if defined)
-- -----------------------------------------------------------------------------
procedure i2c_stop() is

SSPCON2_PEN = high
while SSPCON2_PEN == high loop end loop
if defined(I2C_HARDWARE_TIMEOUT) then
var word timeout_counter = 0
while (SSPCON2_PEN == high) & (timeout_counter != I2C_HARDWARE_TIMEOUT) loop
timeout_counter = timeout_counter + 1
_usec_delay(1)
end loop
else
-- Without timeout.
while SSPCON2_PEN == high loop end loop
end if

end procedure
-- -----------------------------------------------------------------------------

-- -----------------------------------------------------------------------------
-- i2c_transmit_byte -
-- i2c_transmit_byte. Returns TRUE when OK and FALSE in case of an error or
-- when a timeout occurs (if defined)
-- -----------------------------------------------------------------------------
-- -----------------------------------------------------------------------------
function i2c_transmit_byte(byte in data) return bit is

var bit succes = TRUE

PIR1_SSPIF = false ; clear pending flag
sspbuf = data ; write data

-- wait untill write is finished
while ! PIR1_SSPIF loop end loop

-- get Acknowledge Status Bit _ACKSTAT
-- wich indicates that the slave has responded (or not)
-- if i2c device send an Aknowledge, then ready and OK
if SSPCON2_ACKSTAT == low then
return true -- okay
if defined(I2C_HARDWARE_TIMEOUT) then
var word timeout_counter = 0
while !PIR1_SSPIF & (timeout_counter != I2C_HARDWARE_TIMEOUT) loop
timeout_counter = timeout_counter + 1
_usec_delay(1)
end loop
if (timeout_counter == I2C_HARDWARE_TIMEOUT) then
succes = false
else
-- get Acknowledge Status Bit _ACKSTAT
-- wich indicates that the slave has responded (or not)
-- if i2c device send an Aknowledge, then ready and OK
if SSPCON2_ACKSTAT == low then
succes = true -- okay
else
sspcon1_sspen = false;
sspcon1_sspen = true;
succes = false -- no response
end if
end if
else
sspcon1_sspen = false;
sspcon1_sspen = true;
-- Without timeout.
while !PIR1_SSPIF loop end loop

-- get Acknowledge Status Bit _ACKSTAT
-- wich indicates that the slave has responded (or not)
-- if i2c device send an Aknowledge, then ready and OK
if SSPCON2_ACKSTAT == low then
succes = true -- okay
else
sspcon1_sspen = false;
sspcon1_sspen = true;
succes = false -- no response
end if
end if

return succes

return false -- no response
end if
end function
-- -----------------------------------------------------------------------------

-- -----------------------------------------------------------------------------
-- i2c_receive_byte -
-- -----------------------------------------------------------------------------
-- start receiving of a byte and waits till finished
-- start receiving of a byte and waits till finished or timeout (if defined)
-- if param ACK is true, the byte is acknowledged and next bytes can be received.
-- if param ACK is false, the byte is nacked and a stop *should be* sent.
-- note: this behavior is inverted from the line level and orignal library, but
-- consistent with the i2c_software library.
-- -----------------------------------------------------------------------------
function i2c_receive_byte(bit in ACK ) return byte is

var byte data
-- start receive cyclus and wait till full byte received
SSPCON2_RCEN = high
; delay_10us(10)
while SSPSTAT_BF == low loop end loop

if defined(I2C_HARDWARE_TIMEOUT) then
var word timeout_counter = 0
while (SSPSTAT_BF == low) & (timeout_counter != I2C_HARDWARE_TIMEOUT) loop
timeout_counter = timeout_counter + 1
_usec_delay(1)
end loop
else
-- Without timeout.
while SSPSTAT_BF == low loop end loop
end if

-- send Acknowledge (=low) if STOP=true=high
-- and wait till finishd
SSPCON2_ACKDT = ! ACK
SSPCON2_ACKEN = high
while SSPCON2_ACKEN == high loop end loop
if defined(I2C_HARDWARE_TIMEOUT) then
while (SSPCON2_ACKEN == high) & (timeout_counter != I2C_HARDWARE_TIMEOUT) loop
timeout_counter = timeout_counter + 1
_usec_delay(1)
end loop
else
-- Without timeout.
while SSPCON2_ACKEN == high loop end loop
end if

-- get read data
data = sspbuf
;serial_hw_write(data)

; if ACK == i2c_NACK then _i2c_stopbit end if
return data

end function
-- -----------------------------------------------------------------------------

Expand Down
130 changes: 130 additions & 0 deletions sample/16f1823_i2c_hw_eeprom_serial.jal
@@ -0,0 +1,130 @@
-- ----------------------------------------------------------------------------
-- Title: Test program for i2c_hardware.jal using an I2C EEPROM.
-- Author: Rob Jansen, Copyright (c) 2024..2024, all rights reserved.
-- Adapted-by:
-- Compiler: 2.5r8
--
-- This file is part of jallib (https://github.com/jallib/jallib)
-- Released under the ZLIB license (http://www.opensource.org/licenses/zlib-license.html)
--
-- Description: Level 0 i2c test program using an i2c EEPROM with timeout.
-- When you disconnect one of the I2C pins, reading and writing will fail
-- but because of the use of the timeout, the I2C interface will not hang.
-- The timeout can be set by defining the timeout constant as follows:
-- -) const word I2C_HARDWARE_TIMEOUT = 10_000 ; Timeout is al least 10 ms.
--
-- Sources: Datasheet Atmel AT24C32 2-wire serial EEPROM.
--

include 16f1823

-- This program uses the internal oscillator at 32 MHz.
pragma target clock 32_000_000 -- oscillator frequency
pragma target OSC INTOSC_NOCLKOUT -- Internal Clock
pragma target PLLEN ENABLED -- PLL on to get 32 MHz
pragma target WDT DISABLED -- No Watchdog
pragma target PWRTE ENABLED -- Power up timer enabled
pragma target BROWNOUT DISABLED -- No brownout reset
pragma target FCMEN DISABLED -- No clock monitoring
pragma target IESO DISABLED -- int/ext osc. switch
pragma target LVP ENABLED -- Low voltage programming
pragma target MCLR EXTERNAL -- Reset external
-- Set the internal clock frequency to 32 MHz.
OSCCON_IRCF = 0b1110 -- Set 32 MHz (uses 8 MHz source)
OSCCON_SCS = 0b00 -- Clock determined by FOSC (32 MHz)

-- Enable weak pull up for all unused ports since some inputs are not connected
WPUA = 0b0011_1111 -- Weak pull-up on all pins.
WPUC = 0b0011_1111 -- Weak pull-up on all pins.
OPTION_REG_WPUEN = FALSE -- Enable Weak Pull-Up

enable_digital_io() -- Don't forget this otherwise I2C will not work.

_usec_delay(100_000) -- allow hardware to stabilize

-- I2C setup. IIC pins are already defined in the device file for this PIC.
const word I2C_HARDWARE_TIMEOUT = 10_000 -- At least 10 ms.
const word _i2c_bus_speed = 1 -- 100kHz
const bit _i2c_level = TRUE -- i2c levels (not SMB)
include i2c_hardware
i2c_initialize()

-- USART setup.
alias pin_RX_direction is pin_RX_RC5_direction -- Pin 5 of 14 pin DIP.
alias pin_TX_direction is pin_TX_RC4_direction -- Pin 6 of 14 pin DIP.
const serial_hw_baudrate = 115200
include serial_hardware
include print
include delay
serial_hw_init()

-- Constants.
const byte EEPROM_SLAVE_ADDRESS = 0xAE -- Change when needed.
const byte BYTES_TO_TRANSMIT = 10

-- Variables.
var byte counter, data
var bit all_ok

-- Welcome.
print_string(serial_hw_data, "I2C write/read test using an I2C EEPROM and timeout.\r\n")

forever loop

-- Write the bytes to the first EEPROM address.
i2c_start()
all_ok = i2c_transmit_byte(EEPROM_SLAVE_ADDRESS)
-- Start writing at address 0. Note that the start address is a word.
all_ok = all_ok & i2c_transmit_byte(0x00) -- memory address high byte
all_ok = all_ok & i2c_transmit_byte(0x00) -- memory address low byte

-- Now write the data.
counter = 0
while (counter != BYTES_TO_TRANSMIT) & all_ok loop
counter = counter + 1
all_ok = all_ok & i2c_transmit_byte(counter)
end loop
i2c_stop()

if all_ok then
print_string(serial_hw_data, "All data written to EEPROM.\r\n")
else
print_string(serial_hw_data, "Could not write data to EEPROM.\r\n")
end if

-- Now we should the data we had just written.
i2c_start()
all_ok = i2c_transmit_byte(EEPROM_SLAVE_ADDRESS)
-- Start writing at address 0. Note that the start address is a word.
all_ok = all_ok & i2c_transmit_byte(0x00) -- memory address high byte
all_ok = all_ok & i2c_transmit_byte(0x00) -- memory address low byte
-- Switch to read.
i2c_restart()
all_ok = all_ok & i2c_transmit_byte(EEPROM_SLAVE_ADDRESS | 0b0000_0001)

-- Now read the data and print it.
if all_ok then
print_string(serial_hw_data, "Data read from EEPROM: ")
counter = 0
while (counter != BYTES_TO_TRANSMIT) loop
counter = counter + 1
if (counter == BYTES_TO_TRANSMIT) then
-- Last byte, send NACK.
data = i2c_receive_byte(FALSE)
i2c_stop()
else
-- Not last byte, send ACK.
data = i2c_receive_byte(TRUE)
end if
print_byte_hex(serial_hw_data, data)
serial_hw_data = " "
end loop
print_crlf(serial_hw_data)
else
print_string(serial_hw_data, "Could not read data from EEPROM.\r\n")
end if
print_crlf(serial_hw_data)

delay_1s(1)

end loop

0 comments on commit 1f2a0ce

Please sign in to comment.