Problem with bitbanging I2C to 4-digit LED display

Go To Last Post
33 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi!

 

I have Attiny4313 running with internal oscillator at 128KHz, Fuses (E:FF, H:DF, L:E6) wired with a 4-digit-LED display. The display has TM1637 chip in it. I've used this datasheet for reference for the display.

Bitbanging C code produces expected output on SCL and SDA lines, and TM1637 answers with ACK after every byte, but the display does not show anything.

Attachments below show the oscilloscope view and the "schematic". Included also main.c file, although most is seen below in main function.

 

If I remove the display from the circuit, then ACK signal is missing so it's the TM1637 that produces ACK.

In this display's internal circuit, there are already pullup resistors 5K4 in SDA and 10K in CLK. But without external pullup, lines rise time is too slow. CLK will rise only to 4V till it's droped again. I have added 1K pullup resistors to SCL and SDA to compensate this as you can see in the picture and the oscilloscope view is with them.

Should I remove the external pullup and slow the code to compensate the slow rise time?

What am I missing?

 

int main() 
{
	init();

	i2c_start();
	i2c_send(0x44); //Data command: 01000100
	i2c_stop();

	i2c_start();
	i2c_send(0xc0); //address: 11000000
	i2c_send(0xff); //1st digit all segments on
	i2c_stop();

	i2c_start();
	i2c_send(0x8f); //Display on: 10001000
	i2c_stop();

	while (1) {
		_delay_ms(1);
	}
}

 

Attachment(s): 

This topic has a solution.
Last Edited: Mon. Jan 10, 2022 - 12:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also,

The communication method is not equal to I2C bus protocol totally because there is no slave address.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just Googled for TM1637 C and found this https://github.com/TeWu/TM1637

 

I just created an AS7.0 project.  Configured for t4313 @ 128kHz with SDA=PB5, SCL=PB7.

 

I used a 4-digit Display from Ebay which has pullups.

 

Only built and tested for mega328P.   But I can hook it up to a tiny4313 if you have a problem.

The example seems to work ok.

 

David.

 

Edit.  I have just run this on a tiny4313 @ 8MHz.

My apologies.  I think the ZIP has F_CPU=16MHz.   Please check the Project Properties->C->Symbols.

 

Edit.  If you use 128kHz clock fuse you must set ISP frequency to < 32kHz in Tool->ATMEL-ICE->ISP Frequency

Attachment(s): 

Last Edited: Sun. Jan 9, 2022 - 09:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks David for your effort!

 

Sorry for my noob mistake not telling for first, I don't have Atmel Studio. I'm on Debian and avr-gcc.

#compiler/linker options
CFLAGS	 		= -Os -g -Wall -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -ffunction-sections -fdata-sections $(DEFS) $(INC)
DEFS			= -std=gnu11 -DF_CPU=$(F_CPU) -DBAUD=$(BAUD) -mmcu=$(MCU)
INC			= -iquote $(incdir)
LDFLAGS			= -Wl,-Map,$(builddir)/$(TARGET).map -Wl,--gc-sections -mmcu=$(MCU)

About your zip-file, Atmel Studio might make some magic behind there, because my datasheet of Attiny4313 don't know about TWCR and therefore your code won't compile.

Also, your code is about LCD display. I'm using LED display.

 

Could you point out from the i2c_message.png I attached, what's wrong with it?

 

That Github project also seems worth checking out.

 

yes

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well,  I downloaded the ZIP.  Unzipped it to a fresh directory.   Built and run the the AS7.0 project on a real tiny4313.

 

Apart from configuring pins in pins_config.h and setting Target AVR and F_CPU the AS7.0 project uses the unchanged .C files from GitHub.

But the original Makefile from GitHub should work on Debian.   i.e. edit for AVR, F_CPU and edit pins_config.h to suit t4313.

 

I don't know where you found TWCR.

I was using a Catalex 4-digit LED.  I don't think there is any mention of LCD.  My pcb is similar to your RobotDyn pcb but the resistors are in a different place.

 

David.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ah, sorry I was within wrong zip file in my Downloads. I'll get back to it tomorrow.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, cloned TeWu's repo and got it running. At first noticed that code is running slower than mine. SCL is running at 1,75KHz. Have to dig deeper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Go on.  Does it matter?

 

You are effectively talking from an intelligent AVR to another intelligent MCU i.e. TM1637.

The TM1637 handles the low level keyboard scan, LED multiplexing, ...

The AVR just sends high level "commands"

 

No,  I have not studied that GitHub code.   I would just treat it as a black box.   Call the external functions.   Get on with the rest of life.

I would only worry if the Tiny4313 was running out of memory.

 

Incidentally,   it is wise to say if you are restricted to Debian.

The Atmel / Microchip tools are all intended for Windoze.    Which is why I posted an AS7.0 project.

 

Ok,  MPLABX should work on Linux but it is painful.

 

David.

 

p.s. I only glanced at your "main.c" from #1.   It seemed to abuse USI.   In my opinion,  USI works well for a Slave but is messy when you attempt to be a Master.

I am intrigued.   Does your "main.c" actually work ?   If so,  I will try it for myself.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Morning David,

 

Yes, you are right. The speed like this is irrelevant when one want to get an operational device. I was just curious about was that the case why my attempt didn't work.

 

The main.c #1 is written based on Atmel datasheet Attiny2313A/4313.

The USI two-wire mode is compliant to the Inter IC (TWI) bus protocol, but without slew rate lim-
iting on outputs and without input noise filtering. Pin names used in this mode are SCL and SDA.

 There might be (and probably is) some flaws in it, but it works. With the help of another function for sending address, according to a logic analyzer the #1 does create valid I2C messages.

Last Edited: Mon. Jan 10, 2022 - 10:44 AM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Got it, I interpreted TM1637 datasheet wrong and sent bytes wrong way round.

 

Here's to get something displayed

 

int main()
{
	init();

	i2c_start();
	i2c_send(0x02);
	i2c_stop();

	i2c_start();
	i2c_send(0x03);
	i2c_send(0xff);
	i2c_stop();

	i2c_start();
	i2c_send(0x11);
	i2c_stop();

	while (1) {
		_delay_ms(1);
	}
}

Edited

Last Edited: Mon. Jan 10, 2022 - 04:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note that it's bad practice to have your I2C functions return 'void'.

 

I2C operations can fail - so your functions really need to give some indication of when that happens.

And your calling-code needs to pay attention to that.

 

EDIT

 

See: https://www.avrfreaks.net/commen...

 

And: https://www.avrfreaks.net/commen...

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Mon. Jan 10, 2022 - 02:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brebl wrote:

Got it, I interpreted TM1637 datasheet wrong and sent bytes wrong way round.

 

Here's to get something displayed

 

Nothing was displayed.   It hung the ISP interface preventing subsequent re-programming.

 

TM1637 is not I2C.   But you can send commands with simple primitives like i2c_start(), i2c_write(), i2c_stop().   It is just that many "commands" start with an odd number.   e.g. i2c_start(0x03) looks like START with a READ ADDRESS.   At least that is what the GitHub example shows on a Logic Analyser.

 

TM1637 is not a bus-friendly device.   Your DIO line is dedicated to a single device.   I would expect to see open-drain on the bus lines.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

Note that it's bad practice to have your I2C functions return 'void'.

It actually returns bool, but I don't use the return value for anything in this minimal working example.

 

David, I have a hunch that you have your ISP wires attached and it's actually the programmer who's blocking the line and that's why you don't see the output.

It's only a burst of bytes at the beginning of execution. Not continuous message.

 

Similar if you have display attached, it's blocking futher programming.

 

This setting is using open drain.

Last Edited: Mon. Jan 10, 2022 - 03:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

F... Not sure where did I copied that last code snippet. That second send command should probably be 0x03. Not at my workstation right now...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brebl wrote:
It actually returns bool

Ah yes - i2c_send does:

/**
 * @brief master send data byte to slave
 *
 * @param data 8bit
 * @return true slave ack or false slave nack
 */
bool i2c_send(uint8_t data)
{

 

But i2c_start() and i2c_stop() don't:

void i2c_start()
{
	USISR = (1 << USIOIF | (1 << USISIF)); //clear overflow flag and counter

	while (!(SCL_PIN & (1 << SCL_BIT))) { //pullup SCL if it's not up
		USICR |= (1 << USITC);
	}
	PORTB &= ~(1 << PB5); //start
	USICR |= (1 << USITC);
	PORTB |= (1 << PB5);
}

void i2c_stop()
{
	PORTB &= ~(1 << PB5);
	USICR |= ((1 << USITC));
	PORTB |= (1 << PB5); //stop
}

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil,

oh yes that's true.

 

I need to implement something like if other master is holding the line ie. between start and stop then don't send start condition.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

David, you are right about 

TM1637 is not a bus-friendly device. 

Since it's not using addressing you can't really use other devices in the same bus. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It looks incredibly simple to control the TM1637.   You can use regular I2C library functions.   However the TM1637 writes LSB first.   So you have to reverse the bits.

Note that you could write a ready-reversed segment table if you want.

This is a Codevision version.  I will repeat with your USI functions and GCC.

/*
* TM1637_cv.c
*
* Created: 10-Jan-22 16:36:40
*  Author: David Prentice
*/

#include <io.h>
#include <i2c.h>
#include <stdint.h>
#include <delay.h>
#include <string.h>

uint8_t rvs16[] = {
    0x00, 0x08, 0x04, 0x0C, 0x02, 0x0A, 0x06, 0x0E,
    0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x07, 0x0F
};

uint8_t seg7[] = {
    0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 
    0x7F, 0x6F, 0x77, 0x7C, 0x58, 0x5E, 0x79, 0x71
};

uint8_t rvs(uint8_t x)
{
    return (rvs16[x & 0x0F] << 4) | rvs16[x >> 4];
}

uint8_t TM1637_string(uint8_t buf[])
{
    uint8_t i, c;
    i2c_start();
    i2c_write(rvs(0xC0));     //start at digit #0
    for (i = 0; i < 4; i++) { //write segments
        c = buf[i] & 0x0F;
        i2c_write(rvs(seg7[c]));
    }
    i2c_stop();
}

void main(void)
{
    uint8_t buf[4];
    i2c_init();             //100kHz
    i2c_stop();             //start with idle
    i2c_start();
    i2c_write(rvs(0x88));   //CMD = display ON
    i2c_stop();
    while(1)
    {
        TM1637_string(strcpyf(buf, "1234"));
        delay_ms(1000);
        TM1637_string(strcpyf(buf, "5678"));
        delay_ms(1000);
    }
}

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice one, David! yes

 

I'd argue that TM1637 is not compliant to I2C because it expects lsb first.

Or simply their datasheet charts are reversed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The I2C spec does not care about bit order except for the address byte. You simply do what ever the TM1637 requires!

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I2C expects MSB first.  And the LSB of the address determines SLAVE_R or SLAVE_W.

It just looks strange in the Logic Analyser decoder when the "Command" byte appears in the Address position.

 

I had never looked at the TM1637 datasheet.   I had assumed it had many registers, modes, ...

In practice you write all four (or six) digits in one go.   Or you address an individual digit.

 

The keypad function looks interesting.   But my module only contains four 7-segment LEDs.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I made a GCC project using your USI functions.   I put your USI functions into a separate "USI_Brebl.c" file.   I set the F_CPU value in Project Symbols.

 

Your USI functions make no attempt to implement a standard I2C clock speed.   They just rely on the slow F_CPU to provide an acceptable bus speed.   In fact it is the start() and stop() timing that becomes critical at higher F_CPU frequencies.

 

USI is designed for a Slave.   A USI Master requires some form of clock control e.g. delays between USITC wiggles.

 

Why have you chosen 128kHz RC ?

It makes programming "more difficult".   You won't save any battery consumption.  After all the TM1637 is driving LEDs !!

 

However it was an interesting exercise to see how -O1, -O2, ... -Og work on the i2c_stop() function.

-Og uses IN, ORI, OUT instead of SBI.   The current AS7.0 defaults to -Og.   This 3-cycle sequence makes USI stop() work at 2MHz when the 2-cycle -Os fails.

/*
 * TM1637_usi.c
 *
 * Created: 10-Jan-22 17:55:04
 * Author : David Prentice
 */ 

//########## set F_CPU in AS7.0 Project Symbols ##########

#if F_CPU == 1280000     // i.e. running on 128kHz RC
#define CLKDIV 0
#elif F_CPU >= 8000000   // else we prescale from 8MHz RC
#define CLKDIV 0
#elif F_CPU >= 4000000
#define CLKDIV 1
#elif F_CPU >= 2000000
#define CLKDIV 2
#elif F_CPU >= 1000000
#define CLKDIV 3
#elif F_CPU >= 500000
#define CLKDIV 4
#elif F_CPU >= 250000
#define CLKDIV 5
#elif F_CPU >= 125000
#define CLKDIV 6
#elif F_CPU >= 62500
#define CLKDIV 7
#endif

#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
#include <string.h>
#include <stdbool.h>
#include <avr/power.h>    //clock_prescale works with -O0, -O1, -O2, -O3, -Os, -Og

//external USI functions in USI_Brebl.c
void init();              // functions run at clock speed without delays
void i2c_start();         // fail with F_CPU >= 2MHz -Os
void i2c_stop();          // fail with F_CPU >= 2MHz -Os
bool i2c_send(uint8_t c); //e.g. 8.5kHz @ 125kHz -Os. 56kHz @ 1MHz -Os.

uint8_t rvs16[] = {
    0x00, 0x08, 0x04, 0x0C, 0x02, 0x0A, 0x06, 0x0E,
    0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x07, 0x0F
};

uint8_t seg7[] = {
    0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07,
    0x7F, 0x6F, 0x77, 0x7C, 0x58, 0x5E, 0x79, 0x71
};

uint8_t rvs(uint8_t x)
{
    return (rvs16[x & 0x0F] << 4) | rvs16[x >> 4];
}

void TM1637_string(char buf[])
{
    uint8_t i, c;
    i2c_start();
    i2c_send(rvs(0xC0));     //start at digit #0
    for (i = 0; i < 4; i++) { //write segments
        c = buf[i] & 0x0F;
        i2c_send(rvs(seg7[c]));
    }
    i2c_stop();
}

int main(void)
{
    _delay_ms(1000);       //plenty of time for programmer to gain control
    clock_prescale_set(CLKDIV);
    init();
    i2c_start();
    i2c_send(rvs(0x88));   //CMD = display ON
    i2c_stop();
    while(1)
    {
        TM1637_string("1234");
        _delay_ms(1000);
        TM1637_string("5678");
        _delay_ms(1000);
    }
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Last year, I used TM1637- based 4x7S in ATmega48 project based on template in this Zip file.

I reshaped it for ASM, works nice for me.

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi David!

 

Yeah, this was ver0.0.1 of bitbanging and internal 128kHz was chosen only because of simplicity. I had difficulties to get any output from LED display. But now that's sorted so I can start to fine tune it.

I guess TM1637 would respond with ACK whenever it receives 8 bits, it doesn't make any checking about that byte. Every nonsense byte get ACK response.

Last Edited: Wed. Jan 12, 2022 - 12:42 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I tried the 0x48 command.  i.e. Test mode.   That returns a NAK.   And I could not see anything happening.

I expect that "illegal" commands might NAK but I have not tried them.

 

USI works pretty well as a Slave.   It seems easier to bit-bang a Master.

 

So you only chose 128kHz RC to make programming difficult !!

 

What 7-seg LED module are you using ?

 

I connected a scope to my Catalex module and discovered 5us risetime on the SDA line and 8us on the SCL line.

The pullups are marked 103 but the SDA pullup measures (in-circuit) as 5k3.

But the weird thing is : two fat capacitors on the SDA, SCL lines.  

If RC = 10000 * C = 8us this means C = 8us / 10k = 0.8nF on SCL

C = 5us / 5.3k = 0.9nF on SDA line.

 

I suspect that these will be nominal 1nF MLC capacitors.   But WHY ??

I can either remove the capacitors to make the Open-Drain I2C bus work within spec.

Or write a push-pull driver.   (that might get into a fight if the TM1637 gets out of sync)

 

Incidentally Google found a video about TM1637 modules.  https://www.youtube.com/watch?v=...

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My RobotDyn module has similar internal resistors and caps as your module. I use additional external 1K pullup resistors on SCL and SDA and have 7.65kHz SCL freq. with 22us risetime on SCL and SDA.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Without those ext.resistors I get to only about 4volts on SCL, as you can see here.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That looks like RC=40us on the SCL line.   C = 40us / 1k = 40nF

 

So the Chinese companies just place random C on the signal lines.

Did you watch the video.   Some even swap the R and C !!

 

My 1nF is not a killer.  But 40nF is ridiculous.

 

I will write a push-pull driver later.   It is easier for users to change library than to unsolder SMD capacitors.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, can't succeed in every time. Btw nice zoom in that camera.

Last Edited: Wed. Jan 12, 2022 - 05:17 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
It is easier for users to change library than to unsolder SMD capacitors.

Na, just lay your soldering iron across the SMD chip and add enough solder to cover both ends, it will then swipe right off!

 

Keys to wealth:

Invest for cash flow, not capital gains!

Wealth is attracted, not chased! 

Income is proportional to how many you serve!

If you want something you've never had...

...you must be willing to do something you've never done!

Lets go Brandon!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice, pro tip.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Okey, there's my sample. laugh

Last Edited: Wed. Jan 12, 2022 - 10:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice animation.   Ok,  you could run that with a very slow F_CPU.

 

I am intrigued.  Looking on Ebay,  most modules have a central colon instead of individual periods.   But they all seem to have fat MLC capacitors on the signal lines.

Perhaps the TM1637 is sensitive to noise glitches.   I can understand 1nF but you have 40nF which seems excessive.

 

David.