From 1f2a0cea0489bf24d6a0d76904c64365e3b1205c Mon Sep 17 00:00:00 2001 From: Rob Jansen <12682653+RobJansen62@users.noreply.github.com> Date: Sat, 24 Feb 2024 10:12:59 +0100 Subject: [PATCH] Update of i2c hardware adding a timeout feature Preventing a hangup in case something goes wrong on the i2c bus. --- CHANGELOG | 2 + TORELEASE | 1 + include/peripheral/i2c/i2c_hardware.jal | 138 +++++++++++++++++++----- sample/16f1823_i2c_hw_eeprom_serial.jal | 130 ++++++++++++++++++++++ 4 files changed, 243 insertions(+), 28 deletions(-) create mode 100644 sample/16f1823_i2c_hw_eeprom_serial.jal diff --git a/CHANGELOG b/CHANGELOG index bae833de5..d45d3b915 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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: - @@ -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: - diff --git a/TORELEASE b/TORELEASE index 37455b0f5..9df2abdfc 100755 --- a/TORELEASE +++ b/TORELEASE @@ -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 diff --git a/include/peripheral/i2c/i2c_hardware.jal b/include/peripheral/i2c/i2c_hardware.jal index d8f98bfff..1b9f0c391 100644 --- a/include/peripheral/i2c/i2c_hardware.jal +++ b/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) @@ -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 @@ -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 -- ----------------------------------------------------------------------------- diff --git a/sample/16f1823_i2c_hw_eeprom_serial.jal b/sample/16f1823_i2c_hw_eeprom_serial.jal new file mode 100644 index 000000000..e669753c8 --- /dev/null +++ b/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 \ No newline at end of file