Sensirion SHT7X Protocol Issue With C8051F320 Digital Input Line
06/171/2020 | 01:49 PM
Greetings,
Per the Sensirion spec for the SHT7X there is an ACK bit which is sent from the sensor to the device by pulling SDA line low when it is ready. The delay can be 10 to 390 ms.
When I poll the SDA line, P1.0, it never shows as HIGH or '1' to the 8051, even though the scope indicates the signal is clearly HIGH. Oddly, the 8051 begins to register that the SDA line is pulled low well before it actually goes low. See the attached figure. Teal color trace is the logic analyzer, red for scope of SDA line.
It looks this (the sensor supported) is not a standard I2C interface.
I suggest you try bit-bang software implementation to communicate with the SHT7X sensor.
0
Hi Joe,
Looking at the datasheet for the sensor I found this excerpt at the bottom of page 2:
"2.2 Serial Interface (Bidirectional 2-wire)
The serial interface of the SHTxx is optimized for sensor readout and power consumption and is not compatible with I 2C interfaces, see FAQ for details."
So it sounds like Luweiguo was right and that this is not a standard I2C interface. If this is their own custom interface, then yes the best way forward is most likely to bit bang your own implementation of their protocol.
Regards,
Corey
0
Thank you Luweiguo and Corey for writing and taking the time to read the data sheet for the SHT75. I am aware of the incompatibility with I2C and the need for bit banging.
The issue is that the chip appears to be confused about the signal at the input latch for the SDA line when it is floated high for the ACK/NACK from the sensor when the a2d conversion is complete. The sensor will pull SDA low after the command byte is received and when it's ready. SDA appears to be different in the real time execution from the scope traces.
What can cause this apparent input latch issue with the C8051F320?
0
crossbar?
0
This sounds very strange.
to make the GPIO work, you need enable crossbar.
I have no idea why you could not detect the pin level if you have already enabled the cross bard in your code.
0
Crossbar is enabled per the GPIO example I modified for this test.
Note the scope trace and the code in the first post. Cursor 2 of the scope trace shows that we are escaping the polling code and starting the CLK too soon. The code is a debounce filter: a counter for HIGH and LOW states of the SDA line which passes once 10 LOW's have been counted.
The problem is that when I breakpoint after the counter code, there are no counts for the HIGH state.
sda_count == 0
Which is unexpected because the scope trace for SDA is high at this point in the program execution, which is after Cursor 1 in the scope trace.
0
Hi Joe,
Would you be able to post the full code/project and the schematic of your project? That would help me figure out what the root cause of what you're seeing is.
Regards,
Corey
0
Corey,
Sure thing.
//-----------------------------------------------------------------------------
// F32x_Ports_SwitchesLEDs.c
//-----------------------------------------------------------------------------
// Copyright 2014 Silicon Laboratories, Inc.
// http://developer.silabs.com/legal/version/v11/Silicon_Labs_Software_License_Agreement.txt
//
// Program Description:
// --------------------
// Configure port pins as different types of inputs and outputs
//
// This program demonstrates how to configure the port pins as digital inputs
// and outputs. The C8051F320 target board has two push-button switches
// connected to port pins and and two LEDs. The program constantly checks the
// status of the switches and if they are pushed, it turns on the
// respective LED.
//
//
// How To Test:
// ------------
// 1) Ensure shorting blocks are installed on the following:
// J3: P2.0 - P2.0_SW
// J3: P2.1 - P2.1_SW
// J3: P2.2 - P2.2_LED
// J3: P2.3 - P2.3_LED
// 2) If power the C8051F320-TB from the power adapter, please connect the 9V
// power adapter to P1, ensure J8 Pin2 is connected to Pin3, and J2 is
// installed.
// If power the target board from USB, please connect a USB cable to PC, and
// ensure J11 is installed.
// After power the board, the red PWR LED D3 is on.
// 3) Connect the USB Debug Adapter to J4.
// 4) Compile and download code to the C8051F320-TB by selecting
// Run -> Debug from the menus,
// or clicking the Debug button in the quick menu,
// or pressing F11.
// 5) Run the code by selecting
// Run -> Resume from the menus,
// or clicking the Resume button in the quick menu,
// or pressing F8.
// 6) Push the buttons (P2.0 and P2.1) and see that the LEDs turn on.
//
//
// Target: C8051F320/1
// Tool chain: Simplicity Studio / Keil C51 9.51
// Command Line: None
//
//
// Release 1.1 (JL)
// - Updated descriptions
// - 28 SEP 2014
//
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <c8051f320.h>
//#include <SI_C8051F320_Register_Enums.h>
//-----------------------------------------------------------------------------
// Pin Declarations
//-----------------------------------------------------------------------------
//#define P1_0_SDA_PP P1MDOUT; /* SFR for P1.0 push-pull vs open drain*/
sbit P1_0_SDA = P1^0; /* SFR for P1.0 */
sbit P1_1_SCL = P1^1; /* SFR for P1.1 */
#define del 50 // 50 => 700 us, 1 => 14 us
//-----------------------------------------------------------------------------
// Function Prototypes
//-----------------------------------------------------------------------------
void OSCILLATOR_Init (void);
void PORT_Init (void);
void Delay (int);
char read_sht7x_byte(char ACK);
int update_i2c_temp_sensor(void);
//-----------------------------------------------------------------------------
// SiLabs_Startup() Routine
// ----------------------------------------------------------------------------
// This function is called immediately after reset, before the initialization
// code is run in SILABS_STARTUP.A51 (which runs before main() ). This is a
// useful place to disable the watchdog timer, which is enable by default
// and may trigger before main() in some instances.
//-----------------------------------------------------------------------------
void SiLabs_Startup (void)
{
PCA0MD &= ~0x40;
}
char read_sht7x_byte(char ACK)
{
char byte = 0;
int i;
// P1_0_SDA = 0;
// P1MDOUT &= 0xFE; // SDA as input
for(i=7;i >= 0; i--) {
P1_1_SCL = 1;
byte = byte << 1;
Delay(del);
byte |= P1_0_SDA;
Delay(del);
P1_1_SCL = 0;
Delay(del*3);
}
// P1MDOUT |= 0x01; // SDA as output
if (ACK) {
P1_0_SDA = 0; // pulling low is the ACK
Delay(del*1);
}
// Delay(del);
P1_1_SCL = 1;
Delay(del*1);
P1_1_SCL = 0;
Delay(del);
P1_0_SDA = 1;
Delay(del*6);
return byte;
}
//=============================================================================
// Name: int update_i2c_temp_sensor(void)
//
// Description: Read i2c temp sensor value
//
// Parameters: None
//
// Return: 1 = success, 0 = pending, -1 = failure
//
char res[8];
char nack;
int timeout;
char sda_count;
char nsda_count;
int update_i2c_temp_sensor(void)
{
static int result = 0;
unsigned short val = 0;
char cmd = 5;
char byte = 0;
char crc = 0;
int i;
sda_count = 0;
nsda_count = 0;
timeout = 50; // 50. 390 ms max timeout in reference
nack = 0;
// EA = 0;
// start
// P1_0_SDA = 1;
// P1_1_SCL = 0; // 01 should be the default state
Delay(del*2);
P1_1_SCL = 1; // 11 1 start sequence
Delay(del);
P1_0_SDA = 0; // 10 2
Delay(del);
P1_1_SCL = 0; // 00 .
Delay(del);
P1_1_SCL = 1; // 10 3
Delay(del);
P1_0_SDA = 1; // 11 4
Delay(del);
P1_1_SCL = 0; // 01 5
Delay(del);
// write command byte, 5
cmd = 5; // & 0x0E;
for(i=7;i >= 0; i--) {
P1_0_SDA = cmd >> i & 0x01;
Delay(del);
P1_1_SCL = 1;
Delay(del);
P1_1_SCL = 0;
Delay(del);
}
P1_0_SDA = 1;
Delay(del);
P1_1_SCL = 1;
Delay(del);
// TODO jhg: set SDA as input
// P1_0_SDA_PP = 0;
// P1MDOUT &= 0xFE;
// TODO jhg: delay ?
// P1_0_SDA = 1;
Delay(del);
P1_1_SCL = 0;
Delay(del*10);
// jhg 1
// while (P1_0_SDA == 1) {
// timeout++;
// Delay(del*2);
// if (timeout > 81)
// break;
// }
// jhg 2
while (timeout > 0) { // && !timeout (80 ms)
Delay(63); // 63 => 1 ms
timeout--;
if ((P1 & 0x01) == 0) {
nsda_count++;
if (nsda_count > 10) {
Delay(del*3);
break;
}
}
if ((P1 & 0x01) == 1) {
sda_count++;
}
}
nack = P1_0_SDA;
// P1_0_SDA_PP = 1; // SDA back to push-pull
// P1MDOUT |= 0x01;
P1_1_SCL = 1;
Delay(del);
// end write
// read data byte with ACK
// result |= (0x0F & read_sht7x_byte(1)) << 8; // mask top 4 bits
// result |= read_sht7x_byte(1) << 0;
// crc = read_sht7x_byte(0); // TODO: check crc
res[0] = read_sht7x_byte(1);
res[1] = read_sht7x_byte(1);
res[2] = read_sht7x_byte(0);
// end reads
P1_0_SDA = 1; // default state
Delay(del);
P1_1_SCL = 0;
Delay(del);
// EA = 1;
return(result);
}
//-----------------------------------------------------------------------------
// main() Routine
//-----------------------------------------------------------------------------
void main (void)
{
// Disable Watchdog timer
PORT_Init(); // Initialize Port I/O
OSCILLATOR_Init (); // Initialize Oscillator
update_i2c_temp_sensor();
Delay(500);
P1_1_SCL = 1;
P1_1_SCL = 0;
Delay(500);
P1_1_SCL = 1; // 51 ms
P1_1_SCL = 0;
while (1)
{
}
}
//-----------------------------------------------------------------------------
// Initialization Subroutines
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// OSCILLATOR_Init
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters : None
//
// This function initializes the system clock to use the internal oscillator
// at its maximum frequency.
//
//-----------------------------------------------------------------------------
void OSCILLATOR_Init (void)
{
OSCICN |= 0x00; // OSCICN_IFCN__HFOSC_DIV_1; // div1 0x03, div8 0x00;
// Configure internal oscillator for
// its maximum frequency (12 Mhz)
}
//-----------------------------------------------------------------------------
// PORT_Init
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters : None
//
// This function configures the crossbar and ports pins.
//
// To configure a pin as a digital input, the pin is configured as digital
// and open-drain and the port latch should be set to a '1'. The weak-pullups
// are used to pull the pins high. Pressing the switch pulls the pins low.
//
// To configure a pin as a digital output, the pin is configured as digital
// and push-pull.
//
// An output pin can also be configured to be an open-drain output if system
// requires it. For example, if the pin is an output on a multi-device bus,
// it will probably be configured as an open-drain output instead of a
// push-pull output. For the purposes of this example, the pin is configured
// as push-pull output because the pin in only connected to an LED.
//
// P2.0 digital open-drain Switch 1
// P2.1 digital open-drain Switch 2
// P2.2 digital push-pull LED1
// P2.3 digital push-pull LED2
//-----------------------------------------------------------------------------
void PORT_Init (void)
{
P1MDIN |= 0x03; // P1.0-1.1 Inputs
P1MDOUT = 0x02; // P1.0 SDA Open Drain, P1.1 CLK Push-Pull
// P1 = 0x01;
// P1_1_SCL = 0;
// P1_0_SDA = 1;
P2MDIN |= 0x0F; // Lower four pins on P2 are digital
P2MDOUT = 0x0C; // Enable LEDs as push-pull outputs
// Enable Switches as open-drain
P2 |= 0x03; // Set port latches for P2.0
// and P2.1 to '1'
// Enable crossbar and weak pull-ups
XBR1 = 0x00 | 0x40;
// Weak Pullups enabled (except for Ports whose I/O
///< are configured for analog mode)
// XBR1_XBARE__ENABLED = 0x40
}
//-----------------------------------------------------------------------------
// Delay
//-----------------------------------------------------------------------------
//
// Return Value - None
// Parameters - None
//
// Used for a small pause, approximately 80 us in Full Speed,
// and 10 ms when clock is configured for Low Speed for 500
//
// 160 ns per at full speed
//
// ----------------------------------------------------------------------------
void Delay(int delay)
{
int x;
for(x = 0;x < delay;x)
x++;
}
//-----------------------------------------------------------------------------
// End Of File
//-----------------------------------------------------------------------------
With barnacles and all.
Cheers,
Joe
0
I see one issue
the delay routine is in C and thus the dalay time is unknown
use a timer or make (maybe the smallest ever) assembler module
0
erikm,
The delay is good enough in C. =)
Corey et al.,
Attached is the relevant portion of the schematic. The J3 header is populated and that is my connection to the Sensirion part. We do use the PRTR5V0U2X TVS, though otherwise I am directly connecting to the sensor.
Problem Statement refresher: What can cause the SDA line to read as LOW when it is actually HIGH per the scope trace?
Is it possible the code to check the sensor doesn't run at a time that the sensor is letting that pin float high? That code inside "update_i2c_temp_sensor()" appears to run only once before entering a while(1) loop in main that does nothing. So, maybe it is a timing issue.
You could try toggling a separate pin before and after this code and viewing it on his logic analyzer along with his P0.1 and P0.0 pins and see if in fact the P0.0 goes high in that interval where he is polling but has not yet timed out:
while (timeout > 0) { // && !timeout (80 ms)
Delay(63); // 63 => 1 ms
timeout--;
if ((P1 & 0x01) == 0) {
nsda_count++;
if (nsda_count > 10) {
Delay(del*3);
break;
}
}
if ((P1 & 0x01) == 1) {
sda_count++;
}
}
Regards,
Corey
0
I just noticed something with the implementation of the GPIO SFR registers. It could possibly be incorrect.
In our C8051F320 PortIO example, the SI_SBIT(SW1, SFR_P2, 0); macro expands to #define sbit SW1 = SFR_P2^0.
In your example, you have sbit P1_0_SDA = P1^0;
SFR_P2 is a macro for 0xA0. P1 is an SFR register.
I think the use of an SFR register in the sbit P1_0_SDA = P1^0; of the code is incorrect
I'm not sure how that would affect the code, but it's definitely different than how we normally do it.
0
I have just struggled for the last week trying to figure out why P1 inputs other than P1.0 and P1.1 were not registering a 'high' even when I was physically pulling them high with an external resistor. My code is in assembler and was ported over from a different 8051 processor. Though re-assembled used the EFM8BB2 register definitions provided by SiLabs. Voltage checks on the pins showed they were floating. This was puzzling as I had used the correct P1MDIn and Dout settings. Eventually I found a line in my code: MOV IPH,#00000011b. Problem is that IPH has the same address as P1MDin if not selected via SFR Page. Not saying this is the cause for your issue because you are using C and hopefully such matters are taken care of, but anyhow the symptoms being so similar I just have a gut feel that it may be something to do with the P1 mode register being changed somewhere. I had already verified to myself that P1 inputs work as expected using very simple test code . BTW I too have worked for years with the SHT1x series using bit bang in assembler, though I have moved to SHT2x series after Sensirion kept hiking the prices and frankly think of moving to a different brand as they start doing so again even with the 2 series.(think I'll forgo the 3 series...).
Correct Answer
0
Thanks for the input Karam, those sbit lines using P1 may prove to be the issue then.
0
Greetings.
Picking this back up, I cannot reproduce the problem. Even replacing
(P1 & 0x01)
with
P1_0_SDA
does not cause it to occur anymore. The counts are as expected based on the SDA actual:
sda_count = 8
nsda_count = 11
Perhaps it was a hardware configuration issue or some random compiler issue per the above assembly errata.
I'll mark it as resolved now. Thank you for all your time and efforts, guys.
Sensirion SHT7X Protocol Issue With C8051F320 Digital Input Line
Greetings,
Per the Sensirion spec for the SHT7X there is an ACK bit which is sent from the sensor to the device by pulling SDA line low when it is ready. The delay can be 10 to 390 ms.
When I poll the SDA line, P1.0, it never shows as HIGH or '1' to the 8051, even though the scope indicates the signal is clearly HIGH. Oddly, the 8051 begins to register that the SDA line is pulled low well before it actually goes low. See the attached figure. Teal color trace is the logic analyzer, red for scope of SDA line.
Relevant code snippets:
sda_count and nsda_count are how I tell what the 8051 thinks it is seeing at this step.
What is going on here? Is this normal?
Cheers,
Joe
edit: typo and clarity
The datasheet for the sensor is here:
http://www.farnell.com/datasheets/317085.pdf
It looks this (the sensor supported) is not a standard I2C interface.
I suggest you try bit-bang software implementation to communicate with the SHT7X sensor.
Hi Joe,
Looking at the datasheet for the sensor I found this excerpt at the bottom of page 2:
"2.2 Serial Interface (Bidirectional 2-wire)
The serial interface of the SHTxx is optimized for sensor readout and power consumption and is not compatible with I 2C interfaces, see FAQ for details."
So it sounds like Luweiguo was right and that this is not a standard I2C interface. If this is their own custom interface, then yes the best way forward is most likely to bit bang your own implementation of their protocol.
Regards,
Corey
Thank you Luweiguo and Corey for writing and taking the time to read the data sheet for the SHT75. I am aware of the incompatibility with I2C and the need for bit banging.
The issue is that the chip appears to be confused about the signal at the input latch for the SDA line when it is floated high for the ACK/NACK from the sensor when the a2d conversion is complete. The sensor will pull SDA low after the command byte is received and when it's ready. SDA appears to be different in the real time execution from the scope traces.
What can cause this apparent input latch issue with the C8051F320?
This sounds very strange.
to make the GPIO work, you need enable crossbar.
I have no idea why you could not detect the pin level if you have already enabled the cross bard in your code.
Crossbar is enabled per the GPIO example I modified for this test.
Note the scope trace and the code in the first post. Cursor 2 of the scope trace shows that we are escaping the polling code and starting the CLK too soon. The code is a debounce filter: a counter for HIGH and LOW states of the SDA line which passes once 10 LOW's have been counted.
The problem is that when I breakpoint after the counter code, there are no counts for the HIGH state.
Which is unexpected because the scope trace for SDA is high at this point in the program execution, which is after Cursor 1 in the scope trace.
Hi Joe,
Would you be able to post the full code/project and the schematic of your project? That would help me figure out what the root cause of what you're seeing is.
Regards,
Corey
Corey,
Sure thing.
With barnacles and all.
Cheers,
Joe
I see one issue
the delay routine is in C and thus the dalay time is unknown
use a timer or make (maybe the smallest ever) assembler module
erikm,
The delay is good enough in C. =)
Corey et al.,
Attached is the relevant portion of the schematic. The J3 header is populated and that is my connection to the Sensirion part. We do use the PRTR5V0U2X TVS, though otherwise I am directly connecting to the sensor.
Problem Statement refresher: What can cause the SDA line to read as LOW when it is actually HIGH per the scope trace?
Cheers,
Joe
Hi Joe,
Is it possible the code to check the sensor doesn't run at a time that the sensor is letting that pin float high? That code inside "update_i2c_temp_sensor()" appears to run only once before entering a while(1) loop in main that does nothing. So, maybe it is a timing issue.
You could try toggling a separate pin before and after this code and viewing it on his logic analyzer along with his P0.1 and P0.0 pins and see if in fact the P0.0 goes high in that interval where he is polling but has not yet timed out:
Regards,
Corey
I just noticed something with the implementation of the GPIO SFR registers. It could possibly be incorrect.
In our C8051F320 PortIO example, the SI_SBIT(SW1, SFR_P2, 0); macro expands to #define sbit SW1 = SFR_P2^0.
In your example, you have sbit P1_0_SDA = P1^0;
SFR_P2 is a macro for 0xA0. P1 is an SFR register.
I think the use of an SFR register in the sbit P1_0_SDA = P1^0; of the code is incorrect
I'm not sure how that would affect the code, but it's definitely different than how we normally do it.
Greetings.
Picking this back up, I cannot reproduce the problem. Even replacing
with
does not cause it to occur anymore. The counts are as expected based on the SDA actual:
Perhaps it was a hardware configuration issue or some random compiler issue per the above assembly errata.
I'll mark it as resolved now. Thank you for all your time and efforts, guys.
Cheers,
Joe