Mar 122015
 

0. Foreword

Please, do not post elsewhere the code found here.

– The code should reside in one place and people should use links to my site instead. I have a hard time browsing the Internet for pieces of code sometimes, and finding old or obsolete code instead of links to the original developer is always a pain in the ass.

– I expect people to comment on the code and make optimizations/new developments, especially for student usage. Having this in mind, the code itself is an ongoing process which can be followed by students, from its first state (intelligible, but unoptimized) to its latest state (optimized, but unintelligible). Many people asked me how the code becomes awful and almost impossible to understand. It is my chance to show that I always start from a human approach and I am ending to a computer approach, and that this is always an endeavour that takes time and patience and endless computations and verifications, it doesn’t just come from thin air.

– the last reason is that I collect stats on page visits and downloads, which show me where to insist with open source development and where not to. I cannot collect stats from foreign sites, nor do I wish or have the time to do so.

Thank you!

1. Introduction

I have bought 3 pieces of SKU14220 – DIY 4 Digit LED Electronic Clock Kit Large Screen Red Blue Green LED from banggood.  At ~ US$12, it seemed a good deal and most interesting. Following my experience with the EC1204B clock, I was almost sure the firmware was going to be bad.

Bad is a nice thing to say. The firmware was horrible:

– it doesn’t show day/month/year;

– the setup is tricky and not easy to remember/understand;

– it lacks temperature;

– many people comment it is too bright during the night.

– the STC 15F204EA processor was a bad choice. 4K of flash do not suffice to build a nice and comfortable firmware for the end user, in my experience this design needs an processor with at least 8k flash.

So I decided to modify the latest firmware for the EC1204B clock and make it available for this version, with the necessary modifications.

 

Figure 1 – SKU142210 clock assembled – bottom view

Figure 2 – SKU142210 clock assembled – top view

Figure 3 – SKU142210 clock assembled – rear view

Disclaimer:

The software/firmware presented here is a complete rewrite from scratch, just by observing the schematic and by understanding the way it works. It did not involve any kind of disassembly of the original firmware, reverse engineering or other similar stuff.

I do not condone copying the work of others in any way. However, progress is difficult to achieve these days from zero and it is easier to build up on something that already exists, while respecting the original work and effort.

I advice my readers to buy Banggood kits, the PCB has an excellent quality and all the parts delivered will be used, even with the modifications I suggest in this post. The idea is to start learning soldering and coding using affordable ressources.

 

 2. Hardware modifications


Video 1: The modified clock

After studying the schematic, it became obvious the only thing that needed to be replaced was the processor itself, with another firmware.

I couldn’t find any Atmel processor compatible pin to pin with the original, STC 15F204EA processor. The available place to insert a custom PCB in the processor’s slot is also narrow, so space would be of the essence in choosing a replacement processor.

This is why I have decided to use an ATmega8 AU, which is the smd version of the ATmega8.

Supplementary, a DS18B20 was added to measure temperature along with a photo-transistor, to implement automatic dimming of the display.

 

From a mechanical point of view, the programming pins were designed to go through the plexiglass wall, in order to allow programming of the device even when it is fully enclosed, without the need to disassemble the enclosure (see Figure 3 above).

2.1 Schematic

schematic

 

 2.2 PCB

PCB

 2.3 The actual PCB

20150419_101648

Figure 5. The bottom side of the PCB

Figure 6. The top side of the PCB

 3 Software-firmware

 

The software, or firmware, is a variation of the software used for the EC1204B clock, in a previous post of mine.

Disregarding the process of eliminating pieces of code that did not apply to this particular clock, the two main additions are:

-the implementation of analog reading from a photo-diode, quite straightforward process;

– the implementation of digital dimming a multiplexed display.

A few words about the latter. The problem was to dim the display without having disturbing light shifts.

How it was done: In a multiplexed display, only one digit is lit a a time, with the specific data for that digit. Then, we go to the second digit and so on until the last. Basically we have an eternal cycle of lighting each digit at a time, cycle equal to the number of digits to be lit. What if we would add to this cycle “dead” times, when we don’t lit any digit? The result will be a software pwm on the multiplexed digits, up to the point when all the digits start to tremble. After a few trials, I have discovered that this has to stop at around 50 cycles. After 50 cycles, the dimming process isn’t perceived by the human eye, but it inserts this trembling of the digits.

The relationship between the level of light and the dimming is a simple transformation of the first degree, where dimming is inversely proportional to the level of light. The actual parameters are decided by experimenting. In this particular case, I had:

0 light -> 50 dimming cycles

50 light -> 4 dimming cycles.

The transformation became : cycles = (54-light), which is a rough approximation of the hypothesis above. A supplementary condition is also in effect, after reading the light value:

if (light>50) light=50;

This was necessary from direct observation: at 50 light, the display was too dimmed and the light  value (0-100) didn’t correspond well with the dimming if it was to be used full scale.

 

Feel free to play with the code and try to modify and optimize it. I hope it is easy enough to understand after 1-2 hours of study, in its most intimate details.

 4. How it works

The upper button is the “set” button. The lower button is the “select” button.

 

  • Pressing the Set button will cycle between the DS18B20 temperature sensors present at start up. On the board there is only one sensor, but you may solder several other sensors, all connected in parallel, and the device detects them at start up.
  • Pressing the Select button will enable you to program the microcontroller:
    • A short press (less than 5s) will show the date and month. Pressing it again will show the year. One more press reverts to clock display.
    • A longer press (6-10s) will enter the alarm set mode. Press set to cycle between alarm hour, minutes, alarm enable and memory storage. Press select at each stage to cycle possible values (0-23 for hours, 0-59 for minutes, on/off for enable);
    • A longer press (11-15s) will enter the clock set mode. Press set to cycle between year, month, day, minutes, hours and memory storage. Press select at each stage to cycle possible values;
    • A longer press (16-20s) will enter display mode. Press set to cycle between on, off and memory storage. Press select at each stage to cycle possible values;
    • A longer press (21-25s) will enter the temperature unit select mode. Press set to cycle between C,F and memory storage. Press select at each stage to cycle possible values;

At each stage, the last select key press is the memory storage. After storing selected parameters, the clock will beep. If you fail to store parameters (i.e. you do not press the button), the parameters will be in effect until the next reset of the clock.

5. Download

All the project for Atmel Studio 4, including a PDF with the PCB printout and necessary datasheets for parts (DS1302, DS18B20) can be downloaded here.

Following Bob Pike’s request to have the firmware show temperatures in F instead of C, the code was modified with the possibility to program the temperature display unit. The latest version can be downloaded here.

Last edit: 18.04.2015

6. Semi-automatic software drift compensation

Many people (including me) complain about the DS1302 drift, related to badly manufactured quartz resonators that do not offer a real 32 kHz (32768 Hz) clock.

The drift may become so big, a clock can have easily +- 10 minutes drift in a matter of months. Having two or more clocks in different rooms may easily lead to headaches in the morning when the decision to leave for work becomes a growing pain week after week.

The best practice would be to insert a trimmer capacitor in series with the quartz and set it to obtain a 32768 Hz clock. Even this practice is prone to errors due to:

– bringing a metallic screwdriver next to the trimmer alters its capacity. After setting the trimmer it would change its capacity again, hence the clock;

– normal trimmers are influenced by temperature. Just a little, but enough to generate a 1-minute drift every month or so;

– usually there is no or very little space on the PCB to insert a trimmer properly;

– the DS1302 clock could stop if a trimmer inserted in the circuit.

I have imagined a software solution to the drift, by calculating it in between two clock settings.

I began by asking myself what would be a disturbing drift, and I came with 5 minutes per year at a minimum, as being disturbing. However, setting the clock every year wouldn’t be too much to do, since I already set the clock twice a year when the daylight saving time appears and disappears.

5 minutes per year would mean a drift of roughly 5*60/12=25 seconds a month, less than 1 second per day.

Since the setting is done manually and one could press the set button with a 1 second drift, if the setting would be done after 20 days, it would lead to a supplementary error drift of 365/20=18.25 seconds a year, which is less than a minute per year: it wouldn’t even be noticeable.

The whole idea is to set the clock twice in the same month from a good time source (like your PC, after synchronizing its time with a time server). Why during the same month? Just to make the subroutine that computes time difference easier and less flash consuming. Anyway, there’s less than 1K of flash available for this with this project.

So: you set the clock on the 1st of the month and then you set it again on the 20th or 30th of the month.

The software computes the time difference from the two settings, which were made using a good time source and it will also compute the time difference the DS1302 has run, thus determining the time drift in a given period of time (let’s say 34 seconds in 28 days).

28 days equal 28*24*60=40320 seconds

So we have a drift of 34 seconds every 40320 seconds, which means a drift of 1 second every 1185(.88) seconds.

The clock will remember this drift and the processor will either add or subtract (depending on whether the drift is positive or negative) one second every 1185 seconds, by reading and rewriting the seconds in the DS1302.

Two errors are now in place:

– a 0.88 second (from rounding 1185.88 to 1185) every 40320 seconds, leading to roughly 12 seconds a year: almost non existent;

– a supplementary error drift from manipulation of the setting button of 365/20=18.25 seconds a year: almost non existent either;

In fact, this kind of semi-automatic drift compensation can lead to an error of at most 30 seconds a year, much lower than we could obtain by inserting a trimmer and setting it to achieve a 32kHz clock for the DS1302. We would experience annoying drifts in 10 years of non-stop operation of the clock.

What if we set the clock in different months? The software compares the month and the year of the last setting and the previous one. If these do not match, the clock drift is set to zero and there is no automatic drift compensation. A new clock setting in the same month would trigger a new drift computation and automatic drift compensation.

What if we set the time more than once during the same month? The software would take into consideration the last interval between two settings. If the interval is too low (e.g. 3 days) it could lead to a wrong computation of the drift compensation.

In order to set the drift compensation to zero, please operate two consecutive settings one after the other or by changing the month or year.

This version of the firmware can be downloaded here.

7.  Bug fixes, additions and optimizations

While working on my other project, the EC1204B Led rotating clock and implementing some requests from my readers, I have discovered ways to improve the code and also minor bugs that were addressed.

  • the code to multiplex the data on the display was optimized;
  • there was a bug while displaying temperatures over 100. It was fixed.
  • the colon display was modified. Now the colon displays whenever a second starts instead of displaying it on odd seconds only.
  • Peter Casper had a request to alternatively show the clock and the temperature, for he wishes to use the clock in an infrared-heated room.I thought using the same idea as above, so a new EEMEM variable was defined and if at programming time it is equal to 1, it fires a condition in the main loop that shows the temperature for 1 second every 10 seconds. It displays just with one (or the first) temperature sensor.Some optimizations to the display routine were also made.Because this firmware is about to hit the available flash memory in the mega8515, I had to look closely of the size of the binary data that was about to be burned into the MCU. How to do this, since the HEX file size has nothing to do with the size of the binary code? I have written this morning a Windows tool that converts Intel HEX files to binary and viceversa + it allows for automation…well, silent repetition of a load and save operations. I know, I am lazy, that’s why I wished for automation. Find what it is all about in this post.This version of the firmware can be downloaded here.

8. Combining all latest developments in one and allowing for setup without the need to reprogram the flash

While working on the latest requests for US mode (12H display and Fahnrenheit instead of Celsius), I used EEPROM variables to shorten the code and allow for its implementation. Unfortunately, to change these modes, the clock needed to be reprogrammed.

After many optimizations, I have succeeded to implement a way to set these modes without the need for a programmer.

Toggle between US and EU mode is now done from the menu itself:

  • EU mode displays 24H and Celsius. Setting the clock and alarm shows 24H.
  • US mode displays 12H and Fahnrenheit. Setting the clock and alarm shows 12H with A or P indicator for AM/PM.

Resetting the clock while pressing the SET key will toggle between regular clock display and clock/temperature display (temperature is displayes for 1 second every 10 seconds).

Enjoy downloading the latest firmware, datasheets etc, here.

At this moment, the firmware has 8160 bytes, further development on this firware being almost impossible, at least with the ATmega8.

9. It becomes a never ending story, huh?

Following the latest developments on the EC1204B clock, a new set of improvements to the code were added:

  • the code was optimized, again, to lower its size;
  • the ghost digit disappeared;
  • the clock can measure now negative temperatures on both C and F;
  • toggling between EU and US modes may also be done by keeping SET pressed at startup. It can also be changed from the menu.

Enjoy downloading the latest firmware, datasheets etc, here.

 

 

Last edited 17/01/2017

Feb 072015
 

EC1204B Led Rotating Clock: Schematic, upgrade, firmware

0. Foreword

Please, do not post elsewhere the code found here.

– the code isn’t ready yet:

          – I wish to develop more with several DS1621 sensors on a cable, in order to collect more than one temperature;

– The code should reside in one place and people should use links to my site instead. I have a hard time browsing the Internet for pieces of code sometimes, and finding old or obsolete code instead of links to the original developer is always a pain in the ass.

– I expect people to comment on the code and make optimizations/new developments, especially for student usage. Having this in mind, the code itself is an ongoing process which can be followed by students, from its first state (intelligible, but unoptimized) to its latest state (optimized, but unintelligible). Many people asked me how the code becomes awful and almost impossible to understand. It is my chance to show that I always start from a human approach and I am ending to a computer approach, and that this is always an endeavour that takes time and patience and endless computations and verifications, it doesn’t just come from thin air.

– the last reason is that I collect stats on page visits and downloads, which show me where to insist with open source development and where not to. I cannot collect stats from foreign sites, nor do I wish or have the time to do so.

 

1. Introduction

I have bought 3 pieces of EC1204B clock kits from Banggood.com. At ~ US$10, it seemed a good deal and most interesting.

I have built the first one in ~3 hours and spent another ~6 hours to tutor a pupil of mine into soldering the second one.

The 3rd one had a badly burned firmware and didn’t want to start. Banggood will replace it, but I thought to spend some time and rewrite the firmware, since there were some things in the original firmware I didn’t like and I wished to improve.

Figure 1. Original EC1204B Led Rotating Clock

Moreover, what is the point in having a soldering kit without having the firmware and playing for hours with it afterwards, making modifications to the firmware and learning both soldering and programming?

So, I have decided to make this project and to turn it over to the community. Teachers will have a starting point to use this interesting and cheap kit to tutor their pupils.

More advanced hobbyists will have a full working example, using the excellent PCB coming with the kit.

The whole programming stuff took me 8 full days, including the time to write and post this article.

Later edit:

It took me 8 days to finish the first version, 16 days to the first working one, including errors, modifications and improvements suggested by Philipp Klaus and Sergey, with their kind comments.

Much later edit: After almost two years I still have found improvements to the code.

Disclaimer:

The software/firmware presented here is a complete rewrite from scratch, just by observing the schematic and by understanding the way it works. It did not involve any kind of disassembly of the original firmware, reverse engineering or other similar stuff.

I do not condone copying the work of others in any way. However, progress is difficult to achieve these days from zero and it is easier to build up on something that already exists, while respecting the original work and effort.

I advice my readers to buy Banggood kits, the PCB has an excellent quality and all the parts delivered will be used, even with the modifications I suggest in this post. The idea is to start learning soldering and coding using affordable ressources.

 Video: Modified EC1204B clock, latest version.

2. Modifications to the original schematic

First of all, the kit contains a few spare parts, that we will use to make the actual modifications to the clock.

Why modify the kit?

The clock is powered by an AT89S52 processor, which is an enhanced version of the classic Intel 8051. Pin to pin compatible, this processor is capable of running much faster than the original i8051. However, finding a nice development interface (IDE) and a good programmer for it is quite difficult. So, why not use more a more modern processor?

I found the AT90S8515 as being pin to pin compatible with the AT89S52, with minor differences:

– pins 29, 30 and 31 which have different functions. These pins are not used anyway, so no modifications required here.

– the reset pin (pin 9) is active high on the AT89S52 and low on the AT90S8515. So, a first modification would be required here.

– Pins 12 and 13, tied to the micro switches, should be pulled up with resistors, in order to minimize noise on these pins.

– AT90S8515 runs at 8MHz maximum while the original AT89S52 works at 12 MHz.

Figure 2.Modifications to EC1204B Led Rotating Clock

The  modifications are written on the picture, but I will enumerate them here one more time:

1. Cut wiring on PCB, removing the link from the reset switch to VCC.

2. Solder a piece of wire as shown, linking the reset switch to GND.

3. Desolder C5 and R21. Solder R21 at the place of C5, building a pull-up resistor for the reset pin.

4. Do not solder anything at the place of R21. A capacitor on the reset pin will make the processor unprogrammable through ISP (in-system programming), since the RST signal is handled by the programmer.

5. Solder two resistors between 4.7Ko and 10Ko as shown, in order to pull up the SET and SELECT signals. The kit provides for 3 extra 10Ko resistors. I have used 5.6Ko resistors just because the other ones were not at hand at the time I have make these modifications.

6. Desolder the 12MHz quartz and replace it with an 8 MHz one. AT90S8515 will not run consistently (or will not run at all) at 12 MHz.

Later edit: Don’t do it at home! I wondered for quite some time how an overclocked Atmel processor would perform. So I reverted to the original 12MHz crystal, closed my eyes and powered the circuit after defining the new frequency in the software and recompiling it. Surprise! It really worked!

After a few days, though, I saw some glitches:

– mode 9 of displaying seconds doesn’t work as expected – the leds are completely crazy;

– the connection with “some” DS18B20 stop working after a while: the display shows always 127 (communication error). They start working again after a reset, so…

Still, the processor is as cold as before and, if you’re not too bothered by these two issues, you can test it for several months and post your experience here.

Figure 3. EC1204B Led Rotating Clock with AT90S8515 micro controller

At this time, the clock is ready to be connected to an external programmer. I have used an STK500:

Figure 4. Modified EC1204B Led Rotating Clock connected to a STK500 development board

 

Whichever programmer you wish to use, you need wires to link the programmer’s signals to the clock’s programming pins. The clock’s PCB is clearly marked with all the signals: GND, RST, SCK, MISO, MOSI and VCC.

Grab Atmel Studio 4 from Atmel’s website , WinAVR from SourceForge , configure Atmel Studio to use WinAVR toolchain, load the project below, burn it into your processor and, voilà!

 

3. Modus Operandi (How It Works)

 

Video 1: EC1204 Modus Operandi

This operation manual was updated on 26.02.2017, following the many additions to the firmware during the last two years. All timigs were also recomputed during this rewrite of the operation manual.

Definitions:

SET key: the left key

SELECT key: the middle key

RESET key : the right key

A. Special Startup Mode: Seconds Led Design

Keep pressing both SELECT and SET keys then press shortly RESET. Depress the SELECT and SET keys. You are now into Seconds Led Design menu.

Pressing SELECT, you will see numbers from 1 to 9 corresponding to the 9 ways seconds can be displayed by the circular leds. The leds show a live preview for every display mode.

Pressing SET , the selected mode will be written to memory and you will hear a confirmation beep. The selected mode will be in effect after a power off of the clock

Not pressing any key will revert to Clock Mode after about 10 seconds but the selected mode will not be written to memory, preserving it just until the next reset or power toggle.

B. Special Startup Mode: EU or US mode

Keep pressing the SELECT key then press shortly RESET. Depress the SELECT key. This will toggle between European (EU) and United States (US) modes and will affect the way the clock is displaying information:

EU mode: the clock displays 24h time and temperature in Celsius;

US mode: the clock displays 12h time and temperature in Fahrenheit;

C. Special Startup Mode: temperature toggle every 10 seconds

Keep pressing the SET key then press shortly RESET. Depress the SET key. This will toggle between regular time display mode and a mix of time and temperature. In the latter case, the clock displays the time, except for 0,10,20,30,40 and 50 seconds when it will display the measured temperature for one second. This mode was requested and it can be used in rooms or locations where a visual display of the temperature is important: saunas, datacenters etc.

Only the temperature of the first sensor is displayed in this mode.

 D. To Show The Temperature

While in clock mode, short press SELECT. This will trigger the temperature mode, displaying it for all connected temperature sensors in a loop.

First, the sensor number will be displayed: “tE: 1”. A second press on SELECT will display the corresponding temperature for the first sensor.

If there are multiple DS18B20 sensors connected to the clock (maximum is 5 sensors), pressing SELECT again will display in order “tE: 2” then pressing select again the temperature of the second sensor and so on, in a loop.

EC. To Show The Day, Month and Year

While in clock mode, press SET and depress it as long as “dAtE” is displayed on the screen (<5 seconds). The month and day will be displayed for about 2  seconds.

Alternatively, pressing SET in this mode, you don’t have to wait for “dAtE” to be displayed. A short press will suffice.

Press SET again and the year will be displayed for about two seconds.

Press SET again and the clock will show the time.

 D. To Set The Alarm

While in clock mode, press SET and depress it as long as “S :AL” is displayed on the screen (6-10 seconds).

First you may change the minutes of the alarm by pressing SELECT.

Pressing SET again will display the alarm hour.

You may change the hour of the alarm by pressing SELECT.

Pressing SET again will display “on” or “oFF” depending on the value read at start time from the EEPROM.

You may change if the alarm is on or off by pressing SELECT.

Pressing SET again will write the alarm values into EEPROM and the you will hear a confirmation beep.

Not pressing the final SET will change the alarm time, but it won’t be memorized in EEPROM. Thus, a power toggle or a RESET will revert alarm time to whatever was stored in EEPROM.

E. To Stop The Alarm

Just press either SET, SELECT or RESET to stop the alarm until the next time match will occur. If no button is pressed, the alarm will keep beeping for about 10 minutes.

F. To Set The Time

While in clock mode, press SET and depress it as long as “S :CL” is displayed on the screen (more than 10 seconds).

First you may change the year by pressing SELECT.

Pressing SET again will display the month.

You may change the month by pressing SELECT.

Pressing SET again will display the day.

You may change the day by pressing SELECT.

Pressing SET again will display the minutes.

You may change the minutes by pressing SELECT.

Pressing SET again will display the hour.

You may change the hour by pressing SELECT.

Pressing SET again will write the set time into DS1302 and the you will hear a confirmation beep.

Not pressing the final SET will discard all modifications made and the clock will revert to the previous settings.

Every step has a 10 seconds timeout. Not pressing any key during this interval will revert the clock to time display mode. All the settings will be lost.

 4. Firmware

The code is (almost) fully commented,, because I wanted to make it easy to read, easy to understand, and a possible start for a programming course example.

What can a pupil learn from this nice clock?

Hardware:

– How to solder both through-hole and SMD components, by hand;

– How to arrange for parts on both sides of a PCB and solder them, taking into account the height of each part.

– How to find the best spots to make modifications on a given PCB.

Software:

– How to deal with the special registers of a micro controller;

– How to enable/disable interrupts;

– How to multiplex data on a large bus (12 bits) using interrupts;

– How to use variables, constants, #define and EEPROM variables and to grasp the difference between them;

– How to build custom menus for a given task;

– How to make variables prisoners between two given values;

– How to use 1-wire and 3-wire communications;

– How to dynamically program ports for input or output;

– How to write nice and clean code, easy to debug; The RTC library is a bad example of how to write a library.

– How to program a basic alarm which can be shut off;

– How to deal with bitwise operations. I wrote the program specially using |,||,~,^,&,&&,_BV(), binary and hex values, to show the many ways an operation could be done.

-How to use local and global variables;

I am sure a good tutor will find several other stuff to dissect and learn to willing pupils. The main code is almost fully commented for a better understanding.

 

A. DS18B20 stuff.

You can download the DS18B20 datasheet from here.

The AVR GCC library I have used was written by Davide Gironi and I have made minor modifications on it. Please consult his work at http://davidegironi.blogspot.com

B. DS1302 stuff.

You can download the DS1302 datasheet from here.

5. Final considerations and Downloads

 

The code takes almost all the AT90S8515 flash memory (7.51K out of 8K available), so there is place for small modifications.

There is also place for improvements, if one is interested enough.

Another candidate to replacing AT89S52 would be an ATMega8515, which can run at 16 MHz instead of 8MHz when driven with a crystal. This processor is also able to run at 8MHz with an RC internal oscillator. If programmed as such, we don’t need the crystal and the two capacitors anymore!

I know there is also an other candidate from the ATMega family, compatible pin to pin with AT89S52, but I let to you the burden to browse through datasheets and discover it 😉

 

All the project for Atmel Studio 4 can be downloaded here. It contains an error, see 7. Errata

The schematic of the EC1204B clock can be downloaded here.

AT90S8515 datasheet can be downloaded here.

AT89S52 datasheet can be downloaded here.

 

 6. Improvements to the Code

 

Improvement #1

 

Taking all the lines similar to

 d[10]=0b00010000 | dectobin(seconds-in1*8)*((seconds>= 0) && (seconds<= 7));

, adding a function named bb:

uint8_t bb(uint8_t in11, uint8_t sec1, uint8_t sec2) //function to reduce the code
{
    return dectobin(seconds-in11*8)*((seconds>= sec1) && (seconds<= sec2));
}

and transforming the line into

 d[10]=0b00010000 | bb(in1,0,7);

makes the code drop from 7.6Kbit to 6.5 Kbit.

While the original code would not fit into an ATMega8515, because of the loader which takes some place in the flash, the optimized code fits entirely and there is some space left for further coding.

 

 Improvement #2

Going further ,taking all the block of  lines similar to

d[10]=0b00100001 | dectobin(seconds-in1*8)*((seconds>= 0) && (seconds<= 7));
d[11]=0b10000100 | dectobin(seconds-in1*8)*((seconds>= 8) && (seconds<= 15));
d[12]=0b00010000 | dectobin(seconds-in1*8)*((seconds>= 16) && (seconds<= 23));
d[13]=0b01000010 | dectobin(seconds-in1*8)*((seconds>= 24) && (seconds<= 21));
d[14]=0b00001000 | dectobin(seconds-in1*8)*((seconds>= 32) && (seconds<= 39));
d[15]=0b00100001 | dectobin(seconds-in1*8)*((seconds>= 40) && (seconds<= 47));
d[16]=0b10000100 | dectobin(seconds-in1*8)*((seconds>= 48) && (seconds<= 55));
d[17]=0b00010000 | dectobin(seconds-in1*8)*((seconds>= 56) && (seconds<= 59));

, adding a function named cc:

void cc(uint8_t d0,uint8_t d1,uint8_t d2,uint8_t d3,uint8_t d4,uint8_t d5,uint8_t d6,uint8_t d7, uint8_t in111)
{

            d[10]=d0 | bb(in111,0,7);
            d[11]=d1 | bb(in111,8,15);
            d[12]=d2 | bb(in111,16,23);
            d[13]=d3 | bb(in111,24,31);
            d[14]=d4 | bb(in111,32,39);
            d[15]=d5 | bb(in111,40,47);
            d[16]=d6 | bb(in111,48,55);
            d[17]=d7 | bb(in111,56,59);
}

and transforming all the block of lines into one single line,

cc(0b00100001,0b10000100,0b00010000,0b01000010,0b00001000,0b00100001,0b10000100,0b00010000,in1);

repeating the whole process in all the program, makes the code drop again from 6.5 Kbit to 5.86 Kbit.

 

With just two functions related to the way we display seconds, the code dropped with an astounding 1.74K (21%).

Unreadable code, but no less much smaller than the original!

I dare you to try reducing the code to 4K, while keeping all its features!

 

7. ERRATA

 

The code above (clock.c) contained an error related to DS1302: instead of using dt.date, I have used dt.day. Thus, reading and writing the date showed and wrote in fact the day of the week.

This error has been addressed below.

 

8. More Modifications And More Learning

While looking at the kit, there is something missing on the PCB: a 4-pin connector marked “bluetooth”.

Pin 1: connected to pin 10 of the processor

Pin 2: connected to pin 11 of the processor

Pin 3: GND

Pin 4: VDD

According to the datasheet, Pin10 is RxD and Pin11 is TxD of the embedded UART (AT90S8515) or USART (ATmega8515).

I remembered those HC-05 or HC-06 bluetooth modules available on the market and I realized the producer already thought to a further development of the kit, probably the possibility to set its parameters through bluetooth, from a phone.

While this is an interesting development, it doesn’t imho add too much to a learning curve. It just augments the addiction young people have today to smart phones.

 

Why not put his connector to a better use?

I had at home some DS1621 digital temperature chips, working on I2C.

So, I have created the following schematic:

fig.5

 Figure 5. Schematic for a DS1621 addon card.

 

I fitted the board with the help of  two 4-pin male and female connectors to the clock’s board and started to write code.

What is so interesting in this DS1621 chip? Well, nothing special. It is just another thermometer, using I2C communication.

 

What is special is the fact that ATmega8515, which I have used for this modification:

a) does not have a hardware Two Wire Interface (or TWI) that could be used for I2C communication. Thus, a bit banged library had to be used (a library which recreates by software a whatever protocol, instead of relying on hardware).

b) it can be programmed to run out of its own internal 8MHz oscillator, meaning that the 8MHz crystal I had to changed may be removed permanently.

c) may be clocked by crystals of up to 16MHz, so we could resolder the original 12 MHz crystal (while I don’t see the point of doing this).

In order to maximize speed, an assembler code had to be used. Thanks to Peter Fleury, who has already wrote this library years ago, the implementation became suddenly much easier.

 

The new project for Atmel Studio 4, which includes the I2C  (orTWI)  and the DS1621 AVR GCC libraries can be downloaded here.

ATmega8515 datasheet can be downloaded here.

DS1621 datasheet can be downloaded here.

 

9. Ooops! I Did It Again!

 9.1 First attempt

What an interesting chapter number. 9. Identical to the number of the case switch function I have left you for homework.

Well, I did it again. Couldn’t help myself.

I have already embedded this small video, but here it is again:

 

The first attempt of writing this function is as follows:

     case 9:
            timestamp1=seconds;
            if (timestamp1!=timestamp2)
            {
                if (odd(dt.minute))
                {
                    for(j=0;j<=seconds;j++)
                    {
                        in1=j/8;
                        fillbefore(in1,0xFF);
                        _delay_us(700);
                        d[in1+10]=p2(j-in1*8);
                        fillafter(in1,0x00);
                        _delay_us(700);
                    }
                }
                else
                {
                    for(j=0;j<=seconds;j++)
                    {
                        in1=j/8;
                        fillbefore(in1,0x00);
                        _delay_us(700);
                        d[in1+10]=~p2(j-in1*8);
                        fillafter(in1,0xFF);
                        _delay_us(700);
                    }
                }
            //timestamp1=dt.second;
            timestamp2=dt.second;
            }
            break;

 

The computation is the following:

– if the minutes are odd, we light every led until the current lit led equals the current second, then we turn off all other leds, one by one

– we wait for 700 microseconds

– if the minutes are even, we turn off every led until the current lit led equals the current second, then we lit all other leds, one by one

– we wait for 700 microseconds

The total computing time reaches ~90 milliseconds, there is a rest of 10 milliseconds to have a delay between seconds.

These delays over delays have a negative impact on the way the clock works, especially if another execution thread comes into action:

– the alarm sounds like crap;

– setting the clock becomes a pain in the a** because short key presses are almost never felt, since the clock is “waiting for time to pass”.

 

This led me to reconsider the approach and to try to eliminate the delays, without having a strong visual impact.

  9.2 Second and final attempt

 

Since the display and the leds are multiplexed, and the timer overflow code runs again and again (in fact it runs 1024 times a second), why not use a counter to see how many refreshes were made, divide this counter by 13 and use the result? Why 13? Well, 1024/24=78. We could use the first 60 numbers (0-59) to light up the corresponding led, and wait during the numbers 60 to 78.

This approach would solve more than one issue:

– the delay between two leds is determined by the setting of the timer itself, i.e. by hardware, instead of software (delays);

– the delay between two leds is very precise, since we don’t do for cycles anymore.

– the delay between the end of the last led and the following second is very precise;

– we don’t use delays anymore, so reading keys and sounding an alarm will perform as good as in the other modes of seconds  display (1 to 8).

– the processor is processing slightly more than in the first case, when it had to process A LOT.

 

The second form of the same function is the following:

 

     case 9:     
            j=refresh/13;
            if (odd(dt.minute))
            {
                if (j<=seconds)
                {
                    in1=j/8;
                    fillbefore(in1,0xFF);
                    d[in1+10]=p2(j-in1*8);
                    fillafter(in1,0x00);
                }
            }
            else
            {
                if (j<=seconds)
                {
                    in1=j/8;
                    fillbefore(in1,0x00);
                    d[in1+10]=~p2(j-in1*8);
                    fillafter(in1,0xFF);
                }
            }
            break;

 

There is an issue though: the refresh/13 and the start of a new seconds are almost certainly not in phase. Resetting the refresh counter at 1024 does not mean it will be reset at the same moment a new second is coming. This leads to a shift between showing the seconds dots and displaying the seconds leds. When the seconds reach 59 and before changing to zero again, part of the leds don’t lit anymore.

The solution was to reset the refresh counter at the same time the read second from the DS1302 changed. This was accomplished in the timer overflow routine with a simple code, as follows:

 

ISR(TIMER1_OVF_vect)
{
    uint8_t PV;
//comment up to the next sign in order to use first version of function for seconds #9
//    |
//    |
//    v

    refresh++;
    timestamp1=seconds;
    if (timestamp1==timestamp2)
    {
        if (!refresh_resetted)
        {
            refresh=0;
            refresh_resetted=1;
        }
    }
    else
    {
        refresh_resetted=0;
    }

//    ^
//    |
//    |
//comment from the first sign in order to use first version of function for seconds #9

    switch (digit_addressed)

... blah blah the same thing as in previous versions

    if(digit_addressed>=12) digit_addressed=0;
//comment the following line in order to use first version of function for seconds #9
//    |
//    |
//    v

    timestamp2=dt.second;

//    ^
//    |
//    |
}

 

I have discovered a few errors in the firmware (delays not working) and also typos in this very article.

I wrote also a couple of supplementary functions to lower the code size.

Enjoy downloading the latest firmware, datasheets etc, here.

Comment with your ideas, findings etc.

10. Pulsing display

While setting the alarm or the clock, the display should pulse.

With the current firmware, this doesn’t happen.

How to solve this issue without a too big overhead?

 

Because we are counting refresh cycles, we could display the useful digits on the first half (up to 512) and display the NULL symbol on the second half.

We define two new variables:

uint8_t pulsing=0, showdigit=0;

then we alter the Timer Overflow routine, the part pertaining to digits display:

 

    if (pulsing) showdigit=(refresh<512); else showdigit=1;
    switch (digit_addressed)
    {
        case 0:
        PV=PORTD;PV &= ~_BV(4);PV |= _BV(5);PV |= _BV(6);PV |= _BV(7);PORTD=PV;
        if (showdigit) PORTA = ~(d[0]); else PORTA= ~(SEG_NULL);
        PORTC=0xFF;
        break;
        case 1:
        PV=PORTD;PV |= _BV(4);PV &= ~_BV(5);PV |= _BV(6);PV |= _BV(7);PORTD=PV;
        if (showdigit) PORTA = ~(d[1]); else PORTA= ~(SEG_NULL);
        PORTC=0xFF;
        break;
        case 2:
        PV=PORTD;PV |= _BV(4);PV |= _BV(5);PV &= ~_BV(6);PV |= _BV(7);PORTD=PV;
        if (showdigit) PORTA = ~(d[2]); else PORTA= ~(SEG_NULL);
        PORTC=0xFF;
        break;
        case 3:
        PV=PORTD;PV |= _BV(4);PV |= _BV(5);PV |= _BV(6);PV &= ~_BV(7);PORTD=PV;
        if (showdigit) PORTA = ~(d[3]); else PORTA= ~(SEG_NULL);
        PORTC=0xFF;
        break;
        ...the rest of the function is the same as before

 

The main routine has also to be altered, in order to set the modes where we wish the display to be pulsing or not:

 

        //we are focusing on this part of the main function, deciding what to display when there is no key pressed (free running clock)
        //and we add for each display mode a line stating whether we wish a pulsing display or not
        if (key==0)
        {
            //void all long key presses
            switch (ClockMode)
            {
            case SHOWCLOCK:
                if (t2==0)
                {
                    //show clock
                    pulsing=0;
                    dt=get_date_time();
                    digit=dt.hour*100+dt.minute;
                    seconds=dt.second;
                }
                else
                if (t2<=5)
                {
                    //show the date
                    pulsing=0;
                    t1=6000;
                    t2=0;
                      dt=get_date_time();
                    seconds=dt.second;
                    ClockMode=SHOWDATE;
                }
                else
                if (t2<=10)
                {
                    //set the alarm
                    pulsing=1;
                    t1=900;
                    t2=0;
                      dt=get_date_time();
                    seconds=dt.second;
                    ClockMode=SETALMINUTES;
                }
                else
                {
                    //set the date
                    pulsing=1;
                    t1=900;
                    t2=0;
                    dt1=dt;
                    ClockMode=SETYEAR;
                }
                break;    
            case SHOWTEMP:    
                //show temperature until timer expires
                //digit = ds18b20_gettemp();
                pulsing=0;
                digit = get_ds1621_temperature();
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SHOWDATE:
            //show the date
                pulsing=0;
                dt=get_date_time();
                digit=dt.month*100+dt.date;
                seconds=dt.second;
                _delay_ms(1);
                break;    
            case SHOWYEAR:
            //show the year
                pulsing=0;
                dt=get_date_time();
                digit=2000+dt.year;
                seconds=dt.second;
                _delay_ms(1);
                break;    
            case SETHOURS:
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETMINUTES:    
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETDATE:
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETMONTH:
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETYEAR:
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETALHOURS:
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETALMINUTES:
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETAL:
                pulsing=1;
                  dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            case SETSECMODE:
                //read seconds to be displayed
                pulsing=1;
                dt=get_date_time();
                seconds=dt.second;
                _delay_ms(10);
                break;    
            default:
                break;    
            }
        }
        else
        //set key was pressed
        if (key==1)
        {
            if (AlarmOn)
            {
                AlarmOn=0;
            }
            switch (ClockMode)
            {
            case SHOWCLOCK:
                //set to SHOWTEMP and set the timer to ~10 seconds
                t1=900;
                ClockMode=SHOWTEMP;
                break;    
            case SHOWTEMP:    
                //nothing to do    
                break;    
            case SHOWDATE:
                break;    
            case SHOWYEAR:
                break;    
            case SETHOURS:
                pulsing=0;
                t1=900;
                if (++dt1.hour>MaxHours) dt1.hour=MinHours;
                break;    
            case SETMINUTES:    
                pulsing=0;
                t1=900;
                if (++dt1.minute>MaxMinutes) dt1.minute=MinMinutes;
                break;    
            case SETDATE:
                pulsing=0;
                t1=900;
                if (++dt1.date>MaxDate) dt1.date=MinDate;
                break;    
            case SETMONTH:
                pulsing=0;
                t1=900;
                if (++dt1.month>MaxMonth) dt1.month=MinMonth;
                break;    
            case SETYEAR:
                pulsing=0;
                t1=900;
                if (++dt1.year>MaxYears) dt1.year=MinYears;
                break;    
            case SETALHOURS:
                pulsing=0;
                t1=900;
                if (++ALHours>MaxALHours) ALHours=MinALHours;
                break;    
            case SETALMINUTES:
                pulsing=0;
                t1=900;
                if (++ALMinutes>MaxALMinutes) ALMinutes=MinALMinutes;
                break;    
            case SETAL:
                pulsing=0;
                t1=900;
                if (++ALSet>MaxALSet) ALSet=MinALSet;
                break;    
            case SETSECMODE:
                pulsing=0;
                dt=get_date_time();
                seconds=dt.second;
                t1=900;
                if (++SecMode>MaxSecMode) SecMode=MinSecMode;
                break;    
            default:
                break;    
            }
        }
        else
        //select key pressed
        if (key==2)
        {
            if (AlarmOn)
            {
                AlarmOn=0;
            }
            switch (ClockMode)
            {
            case SHOWCLOCK:
                //short key press 1-5 seconds: show date/time for 10 seconds
                //long key press 6-10 seconds: enter alarm set
                //more than 10 seconds pressed: enter date set
                t2++;
                digit=t2;
            case SHOWTEMP:
                break;    
            case SHOWDATE:
                t1=6000;
                ClockMode=SHOWYEAR;
                break;    
            case SHOWYEAR:
                ClockMode=SHOWCLOCK;
                break;    
            case SETHOURS:
                //write the time/date/year into the DS1302
                pulsing=0;
                set_date_time(dt1);
                t1=0;
                beep();
                _delay_ms(200);
                break;    
            case SETMINUTES:    
                ClockMode=SETHOURS;
                break;    
            case SETDATE:
                ClockMode=SETMINUTES;
                break;    
            case SETMONTH:
                ClockMode=SETDATE;
                break;    
            case SETYEAR:
                ClockMode=SETMONTH;
                break;    
            case SETALHOURS:
                ClockMode=SETAL;
                break;    
            case SETALMINUTES:
                ClockMode=SETALHOURS;
                break;    
            case SETAL:
                //write Alarm Values into EEPROM
                pulsing=0;
                eeprom_write_byte(&EALHours,ALHours);
                eeprom_write_byte(&EALMinutes,ALMinutes);
                eeprom_write_byte(&EALSet,ALSet);
                t1=0;
                beep();
                _delay_ms(200);
                break;    
            case SETSECMODE:
                //write SecMode into EEPROM
                pulsing=0;
                eeprom_write_byte(&ESecMode,SecMode);
                t1=0;
                beep();
                _delay_ms(200);
                break;    
            default:
                break;    
            }
        }
        t1--;
        if (t1==0xFFFF) t1=0;
        if (t1==0) ClockMode=SHOWCLOCK;
        display();
        CheckAlarm();
        }

        ...the rest is the same

Enjoy downloading the latest firmware, datasheets etc, here.

Comment with your ideas, findings etc.

11. Multiple DS18B20 on a wire

 

This development took some time because I was waiting for my DS18B20 stack to arrive.

Thanks, Sergey, for commenting on my blog and giving me the necessary push to write the code.

Thanks, Philipp Klaus, for showing me the error in defining the CPU frequency. It helped a lot, especially in the 1-wire protocol, where delays are of the essence and strongly tied to the CPU frequency.

 

The nice little clock is able now to show up to 5 (modifiable variable) DS18B20 sensors, connected in parallel on the same wire.

The program does an automatic search for the sensors and is able to read and display the temperature of each of the sensors it finds at start-up.

Sensors can be plugged in live, while the clock is running, but to detect new sensors the clock must be reset from the reset button.

If the temperature shows at times “85C” it is because this is the initial value of the sensor.

If the temperature shows “127C” it means there is a miscommunication with that particular sensor. Check the cables, solderings etc. It might also mean the sensor is dead, in which case it needs to be replaced.

 

The order the sensors are discovered seems random to the end user. This order is in fact determined by the serial number of each sensor. The good thing is that the sensors are always detected in the same order if they are not changed. Their position on the wire doesn’t matter.

While adding or removing sensors, the order modifies again, but remains the same if no more sensors are added/removed after a reset or power failure. This way, in order to match the software order with the physical order of the sensors, it is enough to rearrange the position of the sensors on the wire so it matches the software detection order.

 

Connecting the sensors must be done by using shielded microphone cables especially for longer distances (more than 3 meters or 9 feet). An alternative would be the use of a 3-wire twisted cable. Check and see what works.

 

What is NOT implemented in software:

– negative temperatures (with a “-” in front of the temperature). I tried putting an ice cube onto one sensor and it got down to…1C after 5 minutes. It didn’t want to get to a lower temperature. The code is however opened to modifications.

– parasitic supply and all the other exhaustive features DS18B20 know about. I’ll leave this for whoever wishes to better the DS18B20 library. It’s not hard. It’s just that I don’t have the time to write a full library for this sensor.

– CRC8. First, it would be a software burden, bearing in mind the flash is almost full with this version of the firmware. Second, you don’t need CRC8 is your sensors are connected with shielded microphone cables of up to 3 meters. Cautin, however, in very noisy environments. Reading could be wrong.

The seconds don’t show accurately on mode 9 while performing temperature readings, mainly because the low level of the one-wire protocol are disabling and re-enabling interrupts. All other functions are working just fine.

This firmware release is not implementing the I2C protocol anymore, neither is it capable to read from the DS1621 anymore. The libraries were removed from the project in order to make space for the larger code of the main program and the new DS18B library. The files remained however in the project’s zip file.

Enjoy downloading the latest firmware, datasheets etc, here.

Comment with your ideas, findings etc.

 

12. Displaying Celsius or Fahrenheit units

I had a request from a US citizen to make the clock display the temperature in Fahnrenheit. I’d like to say to him a public Thank You for the donation he has made.

Since there is not much space left into the mega8515 chip with the latest additions, I wondered a few days on how to implement this but while keeping the Celsius display functionality too.

Writing a code to another menu level would have resulted in a code bigger than the available 8K of flash, so something else had to be done.

In clock.c, a supplementary definition came up:

 

//ECFUnit defines Celsius or Fahrenheit display
//ECFUnit=0 means Celsius Display
//ECFUnit=1 defines Fahnrenheit Display
EEMEM uint8_t ECFUnit=1;
uint8_t CFUnit;

The ECFUnit value is stored in EEPROM and it is read at init time then it is then stored in the CFUnit variable.

When the temperature is read from the sensor (in Celsius, because this is how the sensor is manufactured), the CFUnit is checked and, if it is equal to 1, it will compute the following equation:

digit=digit*9/5+32

basically transforming the Celsius value to a Fahnrenheit value.

So, switching from C to F requires a rewrite of just the EEPROM values. I guess that, depending on where one lives, one will select the display unit just once in a lifetime.

 

I have checked how it displays up to 255F and I have actually discovered a small hidden bug that I have also addressed.

At temperatures over 100F, the second digit disappeared if it was zero. So I checked that part of the code again handling the temperature and it looked like this:

            temp=digit;
            d[4]= temp/100;
            temp-=d[4]*100;
            d[5]= temp/10;
            temp-=d[5]*10;
            d[6]= temp;
            if (d[4]==0) d[0]=SEG_NULL; else d[0]= seg[d[4]];
            if (d[5]==0) d[1]=SEG_NULL; else d[1]= seg[d[5]];
            d[2]= seg[d[6]];
            d[3]= seg[12];

The code made sense: if the second digit (d[5]) was zero, indeed the second displayed digit (d[1]) was set to NULL, even if the first digit was different than zero.

An interesting bug, I thought I have addressed all the small bugs until now but practice shows that tiny errors can always survive in a code.

So, the code was changed to:

            temp=digit;
            d[4]= temp/100;
            temp-=d[4]*100;
            d[5]= temp/10;
            temp-=d[5]*10;
            d[6]= temp;
            if (d[4]==0) d[0]=SEG_NULL; else d[0]= seg[d[4]];
            if ((d[5]==0) && (d[4]==0)) d[1]=SEG_NULL; else d[1]= seg[d[5]];
            d[2]= seg[d[6]];
            d[3]= seg[12+CFUnit*2];

Now, the second digit is NULL if it is equal to zero AND if the first digit is also equal to zero.

Changing the unit letter from C to F was simple. The “C” letter has an index of 12 and the “F” letter an index of 14. We should display “C” if CFUnit is equal to zero and “F” if CFUnit is equal to 1.

The index of the letter to be displayed becomes then 12+CFUnit*2.

Enjoy downloading the latest firmware, datasheets etc, here.

Comment with your ideas, findings etc.

13. Displaying the temperature every x seconds while displaying the time

Peter Casper had a request to alternatively show the clock and the temperature, for he wishes to use the clock in an infrared-heated room.

I thought using the same idea as above, so a new EEMEM variable was defined and if at programming time it is equal to 1, it fires a condition in the main loop that shows the temperature for 1 second every 10 seconds. It displays just with one (or the first) temperature sensor.

Some optimizations to the display routine were also made.

Because this firmware is about to hit the available flash memory in the mega8515, I had to look closely of the size of the binary data that was about to be burned into the MCU. How to do this, since the HEX file size has nothing to do with the size of the binary code? I have written this morning a Windows tool that converts Intel HEX files to binary and viceversa + it allows for automation…well, silent repetition of a load and save operations. I know, I am lazy, that’s why I wished for automation. Find what it is all about in this post.

Enjoy downloading the latest firmware, datasheets etc, here.

Comment with your ideas, findings etc.

 14. Combining all latest developments in one and allowing for setup without the need to reprogram the flash

While working on the latest requests for US mode (12H display and Fahnrenheit instead of Celsius), I used EEPROM variables to shorten the code and allow for its implementation. Unfortunately, to change these modes, the clock needed to be reprogrammed.

After many optimizations, I have succeeded to implement a way to set these modes without the need for a programmer.

Resetting the clock with the SELECT key pressed will toggle between US and EU mode:

  • EU mode displays 24H and Celsius. Setting the clock and alarm shows 24H.
  • US mode displays 12H and Fahnrenheit. Setting the clock and alarm shows 12H with A or P indicator for AM/PM.

Resetting the clock while pressing the SET key will toggle between regular clock display and clock/temperature display (temperature is displayes for 1 second every 10 seconds).

Resetting the clock while pressing both SELECT and SET keys will enter Seconds Display Mode, where you can choose between the 9 ways to display rotating seconds.

Enjoy downloading the latest firmware, datasheets etc, here.

At this moment, the firmware has EXACTLY 8192 bytes, occupying all the flash memory available on the processor:

 

15. Latest optimizations and additions

Having lots of people testing the software is certainly an asset, as other may discover hidden bugs otherwise impossible to see.

It just happened to one clock that, when the power came down and up again repeatedly, the eeprom section of the clock just erased by itself.

A more in-depth look at the mega8515 datasheet states in fact eeprom corruption in special circumstances, when the voltage is slowly rising, on heavy filtered power sources, or, maybe, when power goes down and up again, leading to the same effect. The MCU executes erratic commands and the voltage is too low for proper eeprom operation, so anything can happen.

Since the code was 100% full, I had to look and identify which are the similar sections of code and write a  procedure to be called insted of repeating the same lines of code. This code was identified in the menu section of the main loop. It was optimized and I got some other ~300 bytes of flash that could be used to implement an eeprom corruption prevention system.

Basically, all constants in the eeprom were defined with separate default values and a crc was implemented. Every eeprom read was checking the crc and every time an eeprom value changed, like the alarm etc, the crc was recomputed and written into the eeprom. If the crc is not the expected one while reading any eeprom value, the whole bunch of eeprom variables are reinitialized with their default values.

The important part of the code:

EEMEM uint8_t ECFUnit=0;
uint8_t CFUnit;
EEMEM uint8_t ESwing=1;
uint8_t Swing;
EEMEM uint8_t EUSMode=0;
uint8_t USMode;
EEMEM uint8_t ESecMode=9;
EEMEM uint8_t EALMinutes=0;
EEMEM uint8_t EALHours=0;
EEMEM uint8_t EALSet=0;
//ECRC and DefCRC have to be recomputed manually after a modification to any of the following eeprom variables:
//ECFUnit, ESwing, EUSMode, ESecMode, EALMinutes, EALHours, EALSet
//ECRC=ECFUnit+ESwing+EUSMode+ESecMode+EALMinutes+EALHours+EALSet
EEMEM uint8_t ECRC=10;
uint8_t DefCRC=10;

 

An example of modifying the default values, like if we wish for and US Mode display by default (AM/PM display, Fahrenheit for temperature) would need the modification of the following variables:

EEMEM uint8_t EUSMode=1;
EEMEM uint8_t ECFUnit=1;

The CRC has to be recomputed and modified:

ECFUnit+ESwing+EUSMode+ESecMode+EALMinutes+EALHours+EALSet=1+1+1+9+0+0+0=12

So these two lines have also to be modified:

EEMEM uint8_t ECRC=12;
uint8_t DefCRC=12;

Enjoy downloading the latest firmware, datasheets etc, here.

 

16. Negative temperatures

This firmware does not use the floating point library, it would become huge. Thus it relies solely on integer operations, most of it unsigned.

Temperatures, however, are signed. Using several sensors and having one outside at winter should display negative temperatures, but at this point it doesn’t.

Since at the moment that I am writing these lines there are negative temperatures outside, I thought to address this final issue.

I have observed that, when the temperature was dropping under zero C, the clock was displaying temperatures counting down from 127.

This latest firmware takes negative temperatures into account and displays them correctly, both with Celsius and Fahnrenheit degrees.

Enjoy downloading the latest firmware, datasheets etc, here.

 

17. Timings

All the latest additions to the firmware made that some timings related to the duration some information was displayed became erroneous. This latest development addresses these errors and corrects them.

Enjoy downloading the latest firmware, datasheets etc, here.

Jul 062014
 

Motorcycle (automobile) tail lights with leds and PWM

 

It is trendy to use leds wherever. On automobiles, but especially on motorbikes, where power is of the essence, going down from 10W (or 32W with break lights on) to ~1W is very important.

I did this for a friend who owns a Yamaha Venture:

 

This is how the tail light looks like on this bike:

 

1. Survey

I have measured the inside of the tail light and I came out with this:

Tail Lights Survey: the thicker border defines the PCB where the leds will be mounted.

The tail lights are also used to light up the license plate. So, two sets of leds should be used: red ones for the position and break lights, and white ones for the license plate lights.

All of these leds should be protected from an possible over voltage coming from the bike.

The schematic looks like this:

White lights are powered directly from the regulator while the red one will be linked to the PWM generator

The final PCB looks like this:

PCB with leds and 12 volts regulator

2. PWM generator

I have decided to build a digital PWM generator instead of a linear one, powered by a LM555.

The reasons were the following:

– LM555 works to a voltage up to 16V. A short voltage spike could easily ruin it;

– There is a need to make adjustments to the position intensity and memorize it somewhere.

– Micro-controllers work at 5V and they are protected easier than a LM555;

– they have almost the same price.

I have decided to use an ATtiny25/45/85 for this project, along with SMD components, in order to have a small PCB for the controller.

The schematic:

Power Stage

Micro-controller stage. Notice the Stop signal voltage being divided with resistors

Keyboard and ISP header

Power Stage

PCB

 The mosfet is able to drive up to 500 leds, connected in parallel rows of 4-5 leds, depending on their voltage, directly from 12V.

The keyboard is not a regular keyboard, but merely contacts on the PCB, which allow for an easy setup of the positions intensity. Shorting the “UP” or “DOWN” keys will increase, respectively decrease the PWM duty of the leds, while also memorizing the value into EEprom. This value will be loaded and used the next time the motorcycle will be powered.

3. The software

The software works if your MCU runs at 8 MHz, using the internal RC oscillator, not divided by 8.

Fuses setup

The program is written in C and compiled using Atmel Studio 6.1:

#include <stdlib.h>
#include <avr/io.h>
#include <avr/eeprom.h>
#define F_CPU 80000000
#include <util/delay.h>

uint8_t EEMEM EPositionValue=10;
uint8_t Def_PositionValue=10;

uint8_t PositionValue=10;
uint8_t StopValue=100;

//keys definition
#define UP        1
#define DOWN     2
#define STOP     4
#define KPIN  PINB

void init(void)
{
//timer0 setup
    TCCR0A = 
    1 << COM0A1 | // normal port operation, PWM disabled 
    0 << COM0A0 |
    0 << COM0B1 | // not inverted 
    0 << COM0B0 |
    1 << WGM00  | // fast pwm 8 bit
    1 << WGM01  ; // fast pwm 8 bit

    TCCR0B = 
    0 << WGM02 |
    0 << CS00  | // prescaler /8 = 250 Hz PWM 
    0 << CS01  |
    1 << CS02  ;

    DDRB  =_BV(0)|_BV(1)|_BV(2)|_BV(3);        //bits 0-3 of PORTB are defined as outputs
    PORTB =_BV(0)|_BV(1)|_BV(2)|_BV(3);        //bits 4-5 of PORTB are defined as inputs
    PositionValue= eeprom_read_byte(&EPositionValue);
    if (PositionValue==0xFF)                //not programmed EEPROM
    {
        PositionValue=Def_PositionValue;
        eeprom_write_byte(&EPositionValue, Def_PositionValue);
    }
}

void setduty( uint8_t duty) 
{
    OCR0A=duty*255/100;
}

void readkeys(void)
{
    //LEFT AND RIGHT keys

    //UP key
    if bit_is_set(KPIN, STOP)
    {
        setduty(100);
    }
    else
    if bit_is_clear(KPIN, UP)
    {
        PositionValue++;
        if (PositionValue>100) PositionValue=100;
        setduty(PositionValue);
        eeprom_write_byte(&EPositionValue, PositionValue);
        _delay_ms(10);
    }
    else
    //DOWNkey
    if bit_is_clear(KPIN, DOWN)
    {
        PositionValue--;
        if (PositionValue<1) PositionValue=1;
        setduty(PositionValue);
        eeprom_write_byte(&EPositionValue, PositionValue);
        _delay_ms(10);
    }
    else    setduty(PositionValue);
}

void inittest()
{
    uint8_t i,j;
    for(j=0;j<3;j++)
    {
        for(i=0;i<100;i++)
        {
            setduty(i);
            _delay_us(500);
        }
        for(i=100;i>0;i--)
        {
            setduty(i);
            _delay_us(500);
        }
    }
}
int main()

{
    init();
    inittest();
    setduty(PositionValue);
    while(1)
    {
        readkeys();
    }
}

Jun 302014
 

Timer for Ultraviolet exposure with UV LEDs and PWM fine tuning

Timer operation video

Timer measurement video

Previous timer projects had only on/off possibilities for the UV leds. Some projects need a finer tuning of the UV value, and I am not talking just about PCBs. Stamps and other things need ultraviolet curing, with setups that cannot be fulfilled using the basic, previous design.

Based on the same hardware as the Common Cathode timer available in this post, I have further developed the software in order to use the same hardware to achieve finer results.

This is how it works:

 1. Countdown timer setup mode

When powered, the display shows in minutes and seconds a predefined value of 5:00 (five minutes, zero seconds). The display is blinking and the second dot point is lit, showing that it is in countdown timer setup mode.

This value may be modified using the left and right keys (to decrease or to increase the timer). The steps are measured in seconds. The maximum value that can be programmed is 99 minutes and 59 seconds, just as much as the display can hold with just 4 digits.

The UV leds and the blue witness led are shut off.

Pressing the GO key will enter in countdown timer mode.

2. PWM setup mode

GO and RIGHT keys pressed simultaneously switch the countdown value to the PWM value, expressed in percents (from 0% to 100%).

The default value is 100 (%).

The display is blinking and the fourth dot point is lit, showing that it is in PWM value setup mode.

This value may be modified using the left and right keys (to decrease or to increase the PWM value). The steps are measured in percents. The maximum value that can be programmed is 100%.

The UV leds and the blue witness led are lit at the value defined by the PWM value.

To switch back to displaying the countdown timer, press GO and LEFT keys simultaneously.

Pressing the GO key will enter in countdown timer mode.

3. Countdown Timer mode

Pressing the GO key while in either setup mode (countdown timer, PWM) or in power save mode will enter in countdown timer mode.

The display is steady (does not blink), the second dot point is lit and it shows the minutes and seconds as they go down to zero. The display is reverted to minutes and seconds, no matter which was the mode when the GO key was pressed.

The UV leds and the witness blue led are lit at the defined PWM value;

At the end of the countdown, the UV lights are shut off, the device enters timer countdown setup mode and emits 10 beeps to let the user know it has terminated its timing job.

During Countdown timer mode, the power save mode is disabled and keys functions are also disabled. Upon reaching zero, keys functions are enabled again and power save mode enters automatically in effect after ~ 10 seconds.

4. Power save mode

To save power and to allow the 78L05 regulator to cool down after counting down, the display is shut off after 10 seconds and just the dot point remains blinking. Power save mode works no matter if you are in timer setup mode or in PWM setup mode.

5. Memorizing other values as default values

LEFT and RIGHT keys pressed simultaneously will write in EEPROM both the timer countdown value and the PWM value. set up by the user. At the next power-up, these will be the values used by default for countdown and PWM. After writing these values in EEPROM, the device emits 4 beeps.

6. Other considerations
I can assure you the 78L05 will function very well over time. However, if you feel any doubt, replace it with an 7805 and a small aluminum heat sink.

Any key will do exactly as intended and, if pressed, will disable the power down mode.

During power down, a beep is emitted every ~10 seconds to let the user know the device is waiting, asleep.

The code is written in C and compiles under AVR Studio 4.

The fuses for ATtiny2313 are set as follows:

SELFPREGEN - disabled
DWEN       - disabled
EESAVE     - disabled
SPIEN      - enabled
WDTON      - disabled
BODLEVEL   - brown-out detection disabled
RSTDISBL   - disabled
CKDIV8     - disabled
CKOUT      - disabled
SUT_CKSEL  - Internal RC Oscillator 8MHz; Start-up time: 14CK + 65 ms

 

Common Cathode Display Version: HEX and EEP compiled files enabling you to have a full featured timer, can be found here.

Common Anode Display Version: HEX and EEP compiled files enabling you to have a full featured timer, can be found here.

 

The complete source code for either CC version or CA version can be emailed to those interested in obtaining it. The cost is 10 EUR, payable by paypal, to Cristian Copcea, email copcea at yahoo dot com

Both (CC and CA versions) will be emailed for 15 EUR.

These funds will contribute to acquiring parts, equipment and all other necessary stuff (TIME being the most expensive) to create more interesting devices to be posted in this blog.

 

Make the transaction specifying “UV Timer with PWM source code”, send me an email about this and you will receive in return shortly an email from me with the desired source code(s) attached to the email in a .rar archive.

 

Jun 232014
 

 

PCB exposure with UV Leds and digital countdown timer DIY

 

I have worked for years with a mercury-discharge bulb for exposing PCBs and it started lately to show symptoms of old age.

I have decided to retire the old stuff and to replace it with a modern installation, using UV Leds and a digital timer.

 

A. PCB exposure with Ultraviolet LEDs

The idea is to make a LED matrix, powered at 12V from a regular adaptor.

The matrix should be big enough but not so big as to not having where to range it.

Finally I came up with a matrix of 110×80 mm (4.3×3.2 in), which is enough for most DIY projects.

UV array schematic

The leds, grouped by four, can be powered with 12V directly, since the forwarding voltage of a UV led is 3.3-3.6V.

Actually, at 12V, they are a little bit underpowered, but I see this as a benefit. Even if exposure time is increased a little bit, a digital timer can do the trick and make a sound when the whole process is finished, leaving me to other tasks. For POSITIV 20 PCBs, the initial exposure time was quite strict, which annoyed me because at every error I had to clean the PCB, spray it, leave it to cure for 24 hours – basically every error ate one full day. Prolonging the exposure time to 1:30-2:00 minutes means more finesse and less time and materials and nerves lost.

UV array PCB

The leds should be emitting somewhere around 380-400 nm and they can easily found very cheap on ebay. This array contains 168 UV leds.

B. Timer for the PCB exposure with Ultraviolet LEDs, with a Common Anode, 7 segments display

1. Schematic and PCB

The timer has at its heart an Atmel ATtiny2313 micro-controller that was hanging in a box, along with older and unused AT90S1200 and AT90S2313. These were disturbing me every time I looked into that box. Suddenly, I saw this small project as a challenge to try to return back to the community a few of the things I have learned, by making a small but easy-readable project which would also serve my purpose of retiring the old bulb.

ATtiny1200 was too small for the project, and AT90S2313 lacked one output at first glance.

I have decided then to use the ATtiny2313 to make a small yet effective schematic, compared to those found googling. It seems that little guy did its job well in the end.

The schematic:

Power. Classical schematic with a 78L05, 5V linear regulator

Micro-controller schematic

In the schematic, I have used AT90S2313, which is pin to pin compatible with ATtiny2313, excepting the XTAL lines. ATtiny2313 has a supplementary port, “A”, with three pins, with alternative functions on the XTAL and RESET pins. Two of the port A bits go to the XTAL pins if these are not used to drive a quartz and the third bit goes to the reset pin, if needed.

The buzzer has a 100 ohms resistor in series in order to limit the current drawn from the MCU pin, and a second, 1K, resistor, in parallel, in order to decrease the capacitive load of the buzzer (which is in fact a capacitor which sides are moving while powered). A led (red) shows that there is power. Another 10k resistor is tied to the RESET pin in order to keep it high while operating.

ISP header and keyboard schematic

The ISP header is tied to the MCU classically. The keyboard has just 3 keys, uses the same pins as the ISP and ties these pins to high level through 3 resistors, value 4.7 Kohms. I am using external resistors instead of the internal pull-ups because the internal pull-up resistors have, in my opinion, a too high resistance: 20-50 kohms for I/O pins and 30-60 kohms for the RESET pin.

The power driver for the UV leds

The UV leds are driven by a p-channel mosfet, which is driven by an npn transistor which is driven by the MCU itself. The npn transistor is necessary to shift the 0-5V from the MCU output to an 8-12V output, necessary to drive the gate of the mosfet. I use p-channel mosfets usually because they allow me to keep a common ground between the driver and the load, as opposed to n-channel mosfets. A blue led is the witness on the PCB, to know the UV leds are lit. It is wired in parallel with the whole UV led matrix and it is powered by the mosfet itself.

The p-channel mosfet is enough to drive a steady load of 4.5A at 30V. This is enough for driving directly a load composed of 200 leds powered at 12V, and even more if using PWM.

Common Anode Display Schematic

The display is a common anode one, found in another box, not multiplexed. I have tied all the segment outputs together to make it multiplexed. The display is tied directly to the MCU without using any transistors. Common anodes have 10 ohms resistors in series, to lower the current that passes through the display, without dimming it too much. A regular ATtiny2313 can withstand easily the current going through the display and just gets a little bit warmer while operating.

Actually, there is an error in the footprint of the display: the segments f and g are swapped, it was my error in the footprint design in Eagle. This mistake was solved in software, swapping f and g segments one more time.

If you wish to see the dot point (DP) blinking, to easier differentiate minutes from seconds, just wire the second DP pin to the ground. In the schematic and on the PCB this pin is in the air.

UV Timer PCB

The PCB is one-sided and contains just 8 straps and 1 wire wrapping for the Dot Point.

2. How it works – software

What can be learned from this small project:
- How to program several bits of a port as outputs and the others as inputs;
- How to use multi-dimensional data structures;
- How to program an Atmel MCU timer along with the prescaler in order to have
  well-timed repetitive events;
- How to use an Atmel MCU interrupt routine;
- How to implement a power saving system to preserve our planet from useless
  energy consumption and also to cool down electronic parts by diminishing
  the load while in stand-by;
- How to set a particular bit in a port and how to clear a particular bit in
  a port;
- How to make software timers;
- How to use bit value macros;
- How to read keys and avoid over-reads by using small delays;
- How to reuse pins in an MCU project;
- How to use functions in order to have very small, but intelligible main
  routines;
- How to read and write from/to EEPROM;
- how to alter default values for direct usage upon power-up.

Just read the code, it is in my opinion very readable and self-explainable,
even for a newbie.

 

Operation:

When powered, the display shows in minutes and seconds a predefined value of 5:00. The display is blinking, showing that it is ready to be either programmed, or to start counting.

This value may be modified using the left and right keys (to decrease or to increase the timer). The steps are measured in seconds. The maximum value that can be programmed is 99 minutes and 59 seconds, just as much as the display can hold with just 4 digits.

To memorize in EEPROM the actual value, press LEFT and RIGHT keys simultaneously. At the next power-up, this will be the value used by default for countdown.

When pressing the “GO” key, the system:

– lits the UV leds and the witness blue led;

– starts counting downwards, to 0:00

– the display is not blinking anymore, nor is it shut

At the end of the countdown, the UV lights are shut, the display is blinking again and emits 10 beeps to let the user know it has terminated its job.

 

To save power and to allow the 78L05 regulator to cool down, the display is shut off after 10 seconds and just the dot point remains blinking.

I can assure you the 78L05 will function very well over time. However, if you feel any doubt, replace it with a 7805 and a small aluminum heat sink.

Any key will do exactly as intended and, if pressed, will disable the power down phase.

During power down, a beep is emitted every ~10 seconds to let the user know the device is waiting, asleep.

The code is written in C and compiles under AVR Studio 4.

The fuses for ATtiny2313 are set as follows:

SELFPREGEN - disabled
DWEN       - disabled
EESAVE     - disabled
SPIEN      - enabled
WDTON      - disabled
BODLEVEL   - brown-out detection disabled
RSTDISBL   - disabled
CKDIV8     - disabled
CKOUT      - disabled
SUT_CKSEL  - Internal RC Oscillator 8MHz; Start-up time: 14CK + 65 ms

The code:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#define F_CPU 80000000
#include <util/delay.h>

#define SEG_a 0x01
#define SEG_b 0x02
#define SEG_c 0x04
#define SEG_d 0x08
#define SEG_e 0x10
#define SEG_f 0x20
#define SEG_g 0x40
#define SEG_dot 0x80

uint16_t EEMEM Edigit=800;
uint16_t Def_digit=300;

//global variables for isr()
uint16_t digit, decimal, digit_addressed=0;
uint8_t d[8];
uint8_t pulsing=0;
uint16_t pulseinterval;
uint16_t maxpulseinterval=512;
uint16_t ProgrammedDigit=0;
uint8_t LedOn=0;

//keys definition
#define GO        7
#define RIGHT     6
#define LEFT     5
#define KPIN  PINB
uint8_t keypressed=1;        //a key was pressed
uint8_t t1=0;
uint8_t Powersave=0;

//character definition
unsigned char seg[]=
{
    (SEG_a|SEG_b|SEG_c|SEG_d|SEG_e|SEG_g),             // 0
    (SEG_b|SEG_c),                                     // 1
    (SEG_a|SEG_b|SEG_d|SEG_e|SEG_f),                 // 2
    (SEG_a|SEG_b|SEG_c|SEG_d|SEG_f),                 // 3
    (SEG_b|SEG_c|SEG_c|SEG_f|SEG_g),                 // 4
    (SEG_a|SEG_c|SEG_d|SEG_f|SEG_g),                 // 5
    (SEG_a|SEG_c|SEG_d|SEG_e|SEG_f|SEG_g),             // 6
    (SEG_a|SEG_b|SEG_c),                             // 7
    (SEG_a|SEG_b|SEG_c|SEG_d|SEG_e|SEG_f|SEG_g),     // 8
    (SEG_a|SEG_b|SEG_c|SEG_d|SEG_f|SEG_g),             // 9
    (SEG_dot),                                             // NULL DISPLAY FOR POWER SAVE
};

//interrupt routine for character display
ISR(TIMER0_OVF_vect)
{
    pulseinterval++;
    if (pulseinterval>maxpulseinterval) pulseinterval=0;
    if ((pulsing==1) && (pulseinterval < maxpulseinterval/2))
    {
        PORTB=(0<<DDB0) | (0<<DDB1)| (0<<DDB2)| (0<<DDB3);
    }
    else
    {    
        PORTD = ~(d[digit_addressed]);
        switch (digit_addressed)
        {
            case 0: PORTB=(1<<DDB0) | (0<<DDB1)| (0<<DDB2)| (0<<DDB3);
            break;
            case 1: PORTB=(0<<DDB0) | (1<<DDB1)| (0<<DDB2)| (0<<DDB3);
            break;
            case 2: PORTB=(0<<DDB0) | (0<<DDB1)| (1<<DDB2)| (0<<DDB3);
            break;
            default:PORTB=(0<<DDB0) | (0<<DDB1)| (0<<DDB2)| (1<<DDB3);
        }
        if (LedOn==1) PORTB |= (1<<DDB4);
        digit_addressed++;
        if(digit_addressed>=4) digit_addressed=0;
    }
}

void display(void)
{
    uint8_t min, sec;
    if (Powersave!=1)
    {
        min=digit/60;
        sec=digit-min*60;

        //computing digits
        d[4]=min/10;
        d[5]=min-d[4]*10;
        d[6]=sec/10;
        d[7]=sec-d[6]*10;

        d[0]= seg[d[4]];
        d[1]= seg[d[5]];
        d[2]= seg[d[6]];
        d[3]= seg[d[7]];
    }
    else
    {
        d[0]= seg[10];
        d[1]= seg[10];
        d[2]= seg[10];
        d[3]= seg[10];
    }
}

void init(void)
{
    TCNT0 = 0x00 ;
    TCCR0B = 0x03; //prescaler of 64
    TIMSK = 0x02; //overflow irq req'd
    sei();

    DDRA =1;
    PORTA=7;

    PORTB|=_BV(0)|_BV(1)|_BV(2)|_BV(3)|_BV(4);
    DDRB |=_BV(0)|_BV(1)|_BV(2)|_BV(3);

    PORTD=0xff;
    DDRD =0xff;
    pulsing=1;                //pulsing
    pulseinterval=0;
    digit= eeprom_read_word(&Edigit);
    if (digit==0xFFFF)        //not programmed EEPROM
    {
        digit=Def_digit;
        eeprom_write_word(&Edigit, Def_digit);
    }

                    //set default value 
    display();                //starting value
    ProgrammedDigit=digit;    //if there is no delay programming, that one is the default value of 240s

}

void beep(void)
{
    PORTA=7;
    _delay_ms(10);
    PORTA=6;
    _delay_ms(30);

}

void readkeys(void)
{
    uint16_t i,j;
//LEFT AND RIGHT keys

//LEFT key
    if (bit_is_clear(KPIN, LEFT) && bit_is_clear(KPIN, RIGHT))
    {
        eeprom_write_word(&Edigit, digit);
    }
    else
    if bit_is_clear(KPIN, LEFT) 
    {
        keypressed=1;
        digit--;
        if (digit==0xFFFF) digit=0;
        ProgrammedDigit=digit;
    }
    else
//RIGHT key
    if bit_is_clear(KPIN, RIGHT) 
    {
        keypressed=1;
        digit++;
        if (digit>5999) digit=5999;
        ProgrammedDigit=digit;
    }
    else
        if bit_is_clear(KPIN, GO) 
    {
        keypressed=1;
        Powersave=0;
        PORTA=7;
        _delay_ms(10);
        PORTA=6;
        _delay_ms(30);
        LedOn=1;
        pulsing=0;
        ProgrammedDigit=digit;
        for (i=0;i<ProgrammedDigit;i++)
        {
            _delay_ms(104);
            digit--;
            display();
        }
        digit=ProgrammedDigit;
        display();
        pulsing=1;
        LedOn=0;
        for(j=0; j<10; j++)
        {
            beep();
        }
        pulsing=1;
    }
    else    keypressed=0;

}

void testfordisplaysleep(void)
{
    if (keypressed==0)    //if there is no key pressed
    {
        t1++;            
        if (t1==170)    //wait ~10 seconds
        {
            t1=0;        
            beep();        //beep every ~10 seconds while sleeping
            Powersave=1;//and put display to sleep
        }
    }
    else                //on any key press 
    {    
        t1=0;            
        Powersave=0;    //wake up display
    }
}

int main()

{
    init();
    _delay_ms(10);
    LedOn=0;
    PORTA=6;
    t1=0;
    keypressed=1;
    while(1)
    {
        readkeys();            //read key
        display();            //display number
        _delay_ms(6);        //wait a little bit
        testfordisplaysleep();
    }

}

The complete AVR4 project for the common anode display timer, without the EEPROM functionality, can be downloaded here.

In order to add this functionality to your project, copy and paste the code above over the UV_Timer.c file and recompile with AVR Studio 4.

 

3. The actual device

UV LED matrix

UV Leds and timer assembled

C. Timer for the PCB exposure with Ultraviolet LEDs, with a Common Cathode, 7 segments display

1. Schematic and PCB

The schematic is similar to the first version:

5V Power stage

ATtiny2313 connections

Here, one modification was made: the DP from the display was tied to the A1 pin.

Keyboard and ISP connectors

Mosfet Power Stage

Common Cathode Display

PCB

2. Software

Basically the software is the same as the common anode version, with the following differences:

a. The multiplex routine in ISR works with a negative logic, because of the common cathode. Digits definitions are also updated to the same negative logic. How does it work: we don’t lit anymore the segments to be lit, but we keep them off and lit all the other segments. Because of the polarity reversal on the display, our negative logic is inverted one more time. The result is that the segments defined to be off will in fact lit, leading to a positive image in the end.

b. Supplementary lines were added to solve the issue of the dot point. Since all dot points are wired together, the simpler solution used previously cannot be applied anymore. I have used the A1 bit of the ATtiny2313 to cope with the dot point. This bit must also be part of the multiplex routine, since we wish to lit only the second dot point and keep all other dot points off. Moreover, the A0 bit is used for activating the buzzer in all phases of the display: while on, blinking or in power save mode. This required the usage of specific bit processing while multiplexing. I’ll let you discover in the code the usage of specific bit processing, i.e. “setting the bit” and “clearing the bit”, while all the other bits remain unchanged.

The code:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#define F_CPU 80000000
#include <util/delay.h>

#define SEG_a 0x01
#define SEG_b 0x02
#define SEG_c 0x04
#define SEG_d 0x08
#define SEG_e 0x10
#define SEG_f 0x20
#define SEG_g 0x40
#define SEG_dot 0x80

uint16_t EEMEM Edigit=800;
uint16_t Def_digit=300;

//global variables for isr()
uint16_t digit, decimal, digit_addressed=0;
uint8_t d[8];
uint8_t pulsing=0;
uint16_t pulseinterval;
uint16_t maxpulseinterval=512;
uint16_t ProgrammedDigit=0;
uint8_t LedOn=0;

//keys definition
#define GO        7
#define RIGHT     6
#define LEFT     5
#define KPIN  PINB
uint8_t keypressed=1;        //a key was pressed
uint8_t t1=0;
uint8_t Powersave=0;

//character definition
unsigned char seg[]=
{
    //negative logic
    (SEG_g),                                         // 0
    (SEG_a|SEG_d|SEG_e|SEG_f|SEG_g),                // 1
    (SEG_c|SEG_f),                                     // 2
    (SEG_e|SEG_f),                                     // 3
    (SEG_a|SEG_d|SEG_e),                             // 4
    (SEG_b|SEG_e),                                     // 5
    (SEG_b),                                         // 6
    (SEG_d|SEG_e|SEG_f|SEG_g),                        // 7
    (SEG_dot),                                         // 8
    (SEG_e),                                         // 9
    (SEG_a|SEG_b|SEG_c|SEG_d|SEG_e|SEG_f|SEG_g)     // NULL DISPLAY FOR POWER SAVE
};

//interrupt routine for character display
ISR(TIMER0_OVF_vect)
{
    pulseinterval++;
    if (pulseinterval>maxpulseinterval) pulseinterval=0;
    if ((pulsing==1) && (pulseinterval < maxpulseinterval/2))
    {
        PORTB=(1<<DDB0) | (1<<DDB1)| (1<<DDB2)| (1<<DDB3);
    }
    else
    {    
        PORTD = ~(d[digit_addressed]);

        switch (digit_addressed)
        {
            case 0: {PORTB=(0<<DDB0) | (1<<DDB1)| (1<<DDB2)| (1<<DDB3); PORTA=PORTA&~_BV(1);}     //clear bit 1 port a
            break;
            case 1: {PORTB=(1<<DDB0) | (0<<DDB1)| (1<<DDB2)| (1<<DDB3); PORTA|=_BV(1);}         //set   bit 1 port a
            break;
            case 2: {PORTB=(1<<DDB0) | (1<<DDB1)| (0<<DDB2)| (1<<DDB3); PORTA=PORTA&~_BV(1);}    //clear bit 1 port a
            break;
            default:{PORTB=(1<<DDB0) | (1<<DDB1)| (1<<DDB2)| (0<<DDB3); PORTA=PORTA&~_BV(1);}    //clear bit 1 port a
        }
        if (LedOn==1) PORTB |= (1<<DDB4);
        digit_addressed++;
        if(digit_addressed>=4) digit_addressed=0;
    }
}

void display(void)
{
    uint8_t min, sec;
    if (Powersave!=1)
    {
        min=digit/60;
        sec=digit-min*60;

        //computing digits
        d[4]=min/10;
        d[5]=min-d[4]*10;
        d[6]=sec/10;
        d[7]=sec-d[6]*10;

        d[0]= seg[d[4]];
        d[1]= seg[d[5]];
        d[2]= seg[d[6]];
        d[3]= seg[d[7]];
    }
    else
    {
        d[0]= seg[10];
        d[1]= seg[10];
        d[2]= seg[10];
        d[3]= seg[10];
    }
}

void init(void)
{
    TCNT0 = 0x00 ;
    TCCR0B = 0x03; //prescaler of 64
    TIMSK = 0x02; //overflow irq req'd
    sei();

    DDRA =3;
    PORTA=_BV(0)|_BV(1)|_BV(2);    //all 3 bits of PORT A are set -> DP lit, beep on
    PORTB|=_BV(0)|_BV(1)|_BV(2)|_BV(3)|_BV(4);
    DDRB |=_BV(0)|_BV(1)|_BV(2)|_BV(3);

    PORTD=0xff;
    DDRD =0xff;
    pulsing=1;                //pulsing
    pulseinterval=0;
    digit= eeprom_read_word(&Edigit);
    if (digit==0xFFFF)        //not programmed EEPROM
    {
        digit=Def_digit;
        eeprom_write_word(&Edigit, Def_digit);
    }

                    //set default value 
    display();                //starting value
    ProgrammedDigit=digit;    //if there is no delay programming, that one is the default value of 240s

}

void beep(void)
{
//    PORTA=7;
    PORTA|=_BV(0);            //enable beep
    _delay_ms(10);
//    PORTA=6;
    PORTA=PORTA& ~_BV(0);    //disable beep
    _delay_ms(30);

}

void readkeys(void)
{
    uint16_t i,j;
//LEFT AND RIGHT keys

//LEFT key
    if (bit_is_clear(KPIN, LEFT) && bit_is_clear(KPIN, RIGHT))
    {
        eeprom_write_word(&Edigit, digit);
    }
    else
    if bit_is_clear(KPIN, LEFT) 
    {
        keypressed=1;
        digit--;
        if (digit==0xFFFF) digit=0;
        ProgrammedDigit=digit;
    }
    else
//RIGHT key
    if bit_is_clear(KPIN, RIGHT) 
    {
        keypressed=1;
        digit++;
        if (digit>5999) digit=5999;
        ProgrammedDigit=digit;
    }
    else
        if bit_is_clear(KPIN, GO) 
    {
        keypressed=1;
        Powersave=0;
//        PORTA=7;
        PORTA=_BV(0)|_BV(1)|_BV(2);
        _delay_ms(10);
//        PORTA=6;
        PORTA=_BV(1)|_BV(2);
        _delay_ms(30);
        LedOn=1;
        pulsing=0;
        ProgrammedDigit=digit;
        for (i=0;i<ProgrammedDigit;i++)
        {
            _delay_ms(100);
            digit--;
            display();
        }
        digit=ProgrammedDigit;
        display();
        pulsing=1;
        LedOn=0;
        for(j=0; j<10; j++)
        {
            beep();
        }
        pulsing=1;
    }
    else    keypressed=0;

}

void testfordisplaysleep(void)
{
    if (keypressed==0)    //if there is no key pressed
    {
        t1++;            
        if (t1==170)    //wait ~10 seconds
        {
            t1=0;        
            beep();        //beep every ~10 seconds while sleeping
            Powersave=1;//and put display to sleep
        }
    }
    else                //on any key press 
    {    
        t1=0;            
        Powersave=0;    //wake up display
    }
}

int main()

{
    init();
    _delay_ms(10);
    LedOn=0;
    PORTA=_BV(1)|_BV(2);    //turn off beep, keep DP lit
    t1=0;
    keypressed=1;
    while(1)
    {
        readkeys();            //read key
        display();            //display number
        _delay_ms(6);        //wait a little bit
        testfordisplaysleep();
    }

}

 

 

The complete AVR4 project for the common cathode display timer can be downloaded here.

In order to add this functionality to your project, copy and paste the code above over the UV_Timer.c file and recompile with AVR Studio 4.

The Mosfet used in this project were both 9435 P-channel enhanced mode (30V/4A) Mosfets. The first version used a SOP-8 capsule while the second project used a TO-252 capsule. Since Eagle has parts with the same capsule and pin assignment for other parts, I have found uninteresting to make a library just for 9435. The datasheet for 9435 can be downloaded here.

 

3. The actual device

D. Timer for the PCB exposure with Ultraviolet LEDs, with a Common Cathode, 7 segments display and PWM

This is a further development of the the actual project in order to obtain a full-featured timer, and it is the subject of this post.

 

Jun 222014
 

State Of The Art Single Layer or Double Layer DIY PCB Production

with FR4 or aluminum substrate

 

After many years of DIY electronic projects, I can share my experience about having state of the art electronic PCBs, time after time, with zero or at least minimal initial errors.

I have chosen a double sided PCB as example, with a size of 50×37 mm (1.97×1.46 inches). This will show what to expect if following the instructions below.

If you like my tutorial, you know what to do.

The many things incurred in the process will be explained thoroughly and I wish you all the best results, similar to mine.

 

1. NECESSARY MATERIALS

a. presensitive PCBs or Positive-sprayed PCBs;

Presensitive PCBs are commercially available PCBs covered with a thin layer of photo-sensitive laquer, protected by a plastic layer which doesn’t allow for light to gain the photosensitive layer until the time of etching. These PCBs come in simple or double copper layered models.

Positive-sprayed PCBs are regular PCBs that are covered by YOU with a thin layer of photo-sensitive laquer, using a POSITIV 20 spray, made by KONTAKT CHEMIE, in Germany.

b. Decapant solution for soldering copper tubes. The best I have found is the italian “Saratoga decappante-disossidante” solution, but many other similar may be used;

c. A 30W soldering iron, preferably with a ceramic tip;

d. NaOH, 7% solution, for developing the exposed PCBs;

e. FeCl3, in 30-40% solution, for etching the developed PCBs;

f. metallic thorn (best a tolls special steel);

g. thin saw;

h. cored solder flux

i. Drills. From 0.3mm to 2mm for through-holes;

j. Small drilling machine with a mandrel that can hold drills from 0.3 mm to ~6 mm

k. UV light source.

l. Isopropyl alcohol

2. STEPS TO OBTAIN THE PERFECT PCB

In the following I will demonstrate with a presensitive PCB all the steps described below.

a. CAD ELECTRONIC DRAWING

The CAD electronic drawing is obtained using Eagle, which can be utilized for free for small DIY projects.

Eagle is quite easy to learn and operate. Automatic cabling is a mess, but one can manually route all wiring to obtain very professional drawings to use for PCB manufacturing.

 

Eagle PCB Drawing.

Top and Bottom sides in Black and White

b. PCB DRAWING PRINTOUT

I have used all kind of printers in order to achieve the best results. My experience says that inkjet printers, made by EPSON or SEIKO are among the best. The worst ones are HP printers, because they do not allow for precise customization of the printout.

Prints should be:

– printed on ink-jet transparencies. The rugged surface of the transparency should be the one printed.

– the paper should be selected as “EPSON MATTE” or “PHOTO PAPER MATTE”;

– the quality should be “BEST”;

– the speed should be “LOW”; Always disable high-speed printing;

– the print should be made in black only (or gray-scale);

Printouts.

After printing, cut the drawing with scissors, leaving ~1cm (1/2 inch) on each margin.

After this operation, please shut off the lights in your laboratory, close the blinds if there is strong sun outdoors. You may leave a small light source for environmental lightning, just enough to see what you are doing and nothing else.

b. PCB PREPARATION

There is no need to make special preparations for Presensitive PCBs, as the only thing to do is to remove the blue protective layer just before exposing the PCB.

PCB sprayed with POSITIV 20 should be left to dry for AT LEAST ONE FULL DAY (24 hours). Even if the instructions on POSITIV20 say you can use the PCB right away, in practice I have found this not to be true at all.

Just saw your PCB to be 2-3 mm larger on each side than your drawing.

Put your PCB on a long ruler ( I use a 40cm one) and gently put the transparency drawing over the PCB, aligning the drawing with the PCB. Your drawing should be in the middle of the PCB.

For double-layered PCBs, you should always start with just one side of the PCB. The second side will be dealt with later in this documentations.

c) Preset the timer on your Ultraviolet (UV) light source.

The exposing times are as follows:

– for 100W UV bulbs:

– 2 minutes for presensitive PCBs;

– 30 seconds for POSITIV20 sprayed PCBs;

– for UV leds:

– 6 minutes for presensitive PCBs;

– 1 minute 30 seconds for POSITIV20 sprayed PCBs;

Remember, if you use an UV bulb (bulb with mercury discharge and a buck driver), it should be left running for at least 3 minutes before exposing the PCB. UV Led exposure do not need this time.

Print under the UV exposure device.

Print being exposed to UV light.

Use an ice cream plastic box, filled with enough NaOH solution 7% to cover the PCB.

Put your recently exposed PCB in the box and start shaking until you see the unexposed parts dissolving, leaving the useful wiring visible.

This operation can take 1-4 minutes. Wash thoroughly with a sponge soaked in soap and then rinse with water. You should have a small amount of FeCl3 in a cap. From time to time, pour several drops of FeCl3 on the PCB and see if it starts to etch the PCB. If the PCB is soaked with water, the FeCl3 will expand on the whole surface, leaving marks on the copper that was just developed.

You may also use a head lens to see if there are undeveloped areas on the PCB. Instead of re-immersing the PCB in NaOH, you should use that sponge and wash the PCB again, insisting on the areas that are not attacked by FeCl3, rinsing them, reapplying FeCl3 to see the effect. In rare cases the PCB needs supplementary immersion in NaOH if the development process was insufficient.

The final check is done using a lens.

d) Once the surfaced to be etched were attacked by FeCl3, just discard the NaOH solution (or keep it for future PCBs), rinse the ice-cream box, fill it with FeCl3 to about 2cm (~1 inch) .

Use paper towels to dry the recently developed PCB.

Put it on the surface of the FeCl3 solution, with the side to be etched downwards, and leave it float on the surface of the FeCl3 solution. Indeed, surface forces will keep the PCB afloat and will not let it drown, if the PCB was previously dried well enough.

Etching the top side of the PCB. The PCB is floating on the surface of the liquid, allowing the copper chloride to sink and generating a natural movement of the solution.

 

On single-sided PCBs, one can see how the copper disappears, by checking the PCB, after 10-40 minutes, depending on the concentration of the FeCl3 solution, temperature and width of the copper clad.

On double sided PCBs, one can not see the first layer being etched. The PCB should be first checked after 20 minutes, and every 5 minutes afterwards, until the etching of the first side is complete.

Top side etched

e1) JUST FOR DOUBLE LAYERED PCBs

– use the metallic thorn to mark some holes on all four sides of the PCB;

Marking holes with a metallic thorn

– drill those holes using the right drills; Best to drill large and thin holes too. USE YOUR BEST CUT DRILL BITS. LEAVE THE HOLES AS THEY ARE. YOU MAY DAMAGE THE PRESENSITIVE LACQUER. If the shoulders of the holes are too high, cut them using a drill a little bit larger that the one used to drill the holes, BY HAND.

Holes drilled in the corners of the PCB

Hole shoulder removal using a larger drill, by hand

– develop the second side of the PCB according to the same instructions above;

– REDRAW THE SIDES OF THE HOLES on the second side of the PCB with a permanent marker of the right width and using head lenses, if needed.

Bottom side check after UV exposure and developing

– etch the second side of the PCB according to the same instructions above;

Bottom side etched and cleaned with isopropyl alcohol

e2) JUST FOR ALUMINUM-BASED PCBs.

The ferric chloride reacts heavily with aluminum. Thus, all aluminum surfaces MUST be protected before the etching process. I use duct tape for this, which is very adhesive and doesn’t let the solution to gain the aluminum substrate. Be very careful at the edges of the PCB, where the solution has a stronger tendency to infiltrate.

Also, aluminum-based PCBs cannot be etched afloat. Because of the higher density of the aluminum, the PCB will naturally sink in the solution. These PCBs should be immersed vertically in the solution, using a narrow recipient. The PCB should also be turned upside down every 5 minutes, because the copper chloride that develops during the etching process has a tendency to glue to the lower part of the PCB and the etching process becomes harder in this area.

Aluminum-based PCBs DO NOT SUPPORT THROUGH HOLES. A through hole will connect electrically the aluminum base and the copper clad, rendering the whole PCB unusable.  Aluminium-based PCBs should be used only with copper islands for remote connection, all components should be surface mounted, with no hole drilled.

f) clean thoroughly the PCB with isopropyl alcohol. Cetones could also be used (nail polish remover=acetone) but they are very flammable, they vaporize very quickly and are highly flammable.

g) after having marked all the holes and then drilled them all,  use a cutter to even the holes edges.

h) put soldering paste all over your PCB (one side at a time);

i) use the soldering iron with minute quantities of tin to cover all the tracks on the PCB.

Most people skip this phase, but IMHO it is one of the most important because:

– this will allow later for a better soldering and it will not allow the copper layer(s) to oxidize;

– if one covers the tracks one by one, it is easy to observe shorts or very fine interruptions;

– decapant paste contains also some wax, which will not allow later for the tin to spread from one pin to another so easily;

– in the long run, it prevents copper oxide to appear, because of the humidity in the air combined with different salts;

– it allows for better and faster soldering when planting the parts.

– it obliges a through inspection of every track, allows for quick repairs and avoids taking out parts for later repairs.

Decapant paste on PCB

Tinning the copper tracks

j) wash thoroughly the soldering paste. THOROUGHLY! WITH SOAP AND SPONGE! Soldering paste conducts electricity and you don’t want any bizarre  electrical behavior.

Tinned PCB. Final result on one side

k) mark the holes using the metallic thorn;

l) drill the holes;

Holes drilled with different sizes of drills. Through-holes are drilled with a 0.4 mm drill

m) plant the electronic parts:

– through-holes if double sided;

Through-hole wire soldering

– straps if single sided;

– passive components;

Passive components soldering

– voltage regulators, verify voltages;

– active components (transistors, diodes, ICs).

 

That’s it! Good luck making PCBs!

 

Aug 282011
 

Frecvențmetru 50 MHz

Schema de mai jos este cea a unui frecvențmetru de 50 MHz cu afișaj digital, după o schemă clasică, creat exclusiv cu circuite TTL și CMOS.
Frecvențmetrul poate fi utilizat și pentru a măsura direct quarz-uri dar poate fi utilizat și ca sondă logică de nivel TTL prin intermediul afișajului suplimentar de nivel logic (acesta afișează “0” pentru nivel logic zero, “1” pentru nivel logic 1 și nu afișează nimic dacă sonda este în aer.

Schemă circuit

Schema în format PDF poate fi descărcată de aici.
Aici puteți descărca schema în format Eagle iar aici cablajul în același format (versiunea nouă cu 74LS390).
Aici puteți descărca schema în format Eagle iar aici cablajul în același format (versiunea veche cu 74LS90).

Imagine spate

Imagine față

Imagine sus

Aug 272011
 

After having spent several nights searching in a 1500-users network for a rogue dhcp server, coming from a “smart” user who bought a junk router for Christmas, I have decided it’s time to use our knowledge instead of using our nights to solve this issue.

How it was done:

Two different services work aggregated for this issue:

1. DHCPD ROGUE DETECTOR

it is a smart component which is able to transform any network adapter into a dhcp client adapter, for a limited amount of time. The really smart thing is that it DOES NOT interfere with the IP addresses already assigned to that specific interface, which usually works as gateway – such as during the sniff, the same interface works as gateway as previously assigned.

The component asks for an IP address and remembers the IP address and MAC Address of the DHCP server only if this one is different than that already present on the same interface. The component will NOT bind the IP address asssigned by the dhcp server to the interface, instead it will write to a log and send an email. All parameters are configurable (from, to, mail server etc) and the component is easily distributable as compiled noarch ELF on both i386 and x86_64 architectures.

2. DHCP Slapper

The first component only tells us about a rogue dhcp server in the network, but it does not interfere with it. Without the second component, the rogue dhcp server is able to do its dirty work without any problem.

This is where the dhcp slapper comes into action.

A regular DHCP traffic is as follows:

client looks for dhcp server using broadcast

DHCPDISCOVER from MACADDRESS via ethx

dhcp server offers client via broadcast an IP address

DHCPOFFER on IPADDRESS to MACADDRESS via ethx

client requests IP address via broadcast

DHCPREQUEST for IPADDRESS (DHCPIPADDRESS) from MACADDRESS via ethx

dhcp server acknowledges and lends the IP address to the client via broadcast
DHCPACK on IPADDRESS to MACADDRESS via ethx

The last message can also be the following:

dhcp server does not acknowledge the IP address to the client via broadcast because it detects an IP conflict
DHCPNACK on IPADDRESS to MACADDRESS via ethx

What did we do?

In simple terms, a broadcast is a communication between two computers using MAC addresses instead of using unicast, i.e IP Adresses

Broadcast communication may be computed by any computer in the same subnet because broadcast is essentially a “noise on the wire”. It gets in all the subnet and data is transmitted through all possible ports to all network devices, including computers.

What if we could define a dhcp server authoritative ont for an IP subnet (or several) but instead on an interface and tell this dhcp server to fight any other dhcp server it hears. Since a rogue dhcp server is using the same schematic to talk to a client, it would be enough if, after the rogue DHCP server transmits a DHCPACK signal, our DHCP server would transmit to the same client a DHCPNAK signal. DHCP theory (the RFC defining DHCP operation) states that, in this case, the client should restart all the process with DHCPDISCOVER and so on.

Practice showed that our clients will receive correct IP addresses from this DHCP server after a maximum of 3 DHCPDISCOVERs. Our DHCP server starts to answer quicker and quicker until the client will hear the authoritative DHCP server instead of the rogue one.

While this process comes with a broadcast overhead, it is not important enough as to disturb the network in such a manner as to make communications impossible. It does though kill all rogue DHCP servers, long enough to let us go on the field and disconnect physically the cable going to the rogue DHCP server (the next day) and also allows a correct operation of the clients.

Aug 272011
 

After having been recently bitten by Ethernet’s flow control mechanism, I decided to learn about this somewhat obscure but commonly used facet of modern networks. This post is a summary of what I discovered about it and its associated benefits and dangers.

What is flow control?

Ethernet flow control, or 802.3x, is a way for a network device to tell its immediate neighbor that it is overloaded with data, such as when a device is receiving data faster than it can process it. It allows for an overloaded device to send out a special Ethernet frame, called a pause frame, that asks the device on the other end of the wire to stop sending data temporarily. If the receiving device honors the pause frame then the sending device has time to catch up on the stack of received data that it hasn’t had time to process yet.

There also exists an older method for flow control called “back pressure” that is used in half-duplex environments (i.e. non-switched Ethernet). It consists of the overloaded device “jamming” the medium temporarily until it has the ability to accept more data. I don’t know much about half-duplex flow control, and thus I won’t mention it again; everything here applies solely to full-duplex flow control via 802.3x. Also, TCP has a mechanism for performing its own flow control that is entirely different from Ethernet’s flow control; I will not be fully explaining TCP’s flow control method here, as it would merit a lengthy discussion itself.

Rules of the game

When thinking about Ethernet flow control, it is important to keep several things in mind:

  1. Flow control operates at a lower layer than TCP or IP, and thus is independent of them. Put another way, flow control is capable of being used regardless of what higher-level protocols are put on top of it. An important side-effect of this is that neither TCP nor IP know what Ethernet’s flow control is doing; they operate under the assumption that there is no flow control other than what they may or may not provide themselves.
  2. Flow control functions between two directly connected network devices, and flow control frames are never forwarded between links. Thus, two computers that are connected via a switch will never send pause frames to each other, but could send pause frames to the switch itself (and vice versa: the switch can send pause frames to the two computers).
  3. Pause frames have a limited duration; they will automatically “expire” after a certain amount of time. The expiration time is set by the device that transmits the pause frame.
  4. A paused link is not a discriminator of protocols; it will prevent any data from being passed across the link other than more pause frames.

Perhaps you have begun to see some issues with flow control in light of some of the above points. Let’s start looking at them.

TCP breakage

Okay, it isn’t true, TCP doesn’t stop working when flow control is enabled. However, an important part of it does stop working correctly: its own flow control mechanism. TCP flow control uses a more complex mechanism of timeouts and acknowledgement segments to determine when a remote device is overloaded. It basically sends at a faster and faster pace until it sees that some of its sent data isn’t getting to the remote device and then slows down. This allows TCP to utilize network links in a somewhat intelligent manner, as an overloaded network or device will cause some TCP segments to be lost and thus cause the sender to send data at a slower rate.

Now consider what happens when Ethernet flow control is mixed with TCP flow control. Let’s assume that we have two directly connected computers, one of which is much slower than the other. The faster sending computer starts sending lots of data to the slower receiving computer. The receiver eventually notices that it is getting overloaded with data and sends a pause frame to the sender. The sender sees the pause frame and stops sending temporarily. Once the pause frame expires, the sender will resume sending its flood of data to the other computer. Unfortunately, the TCP engine on the sender will not recognize that the receiver is overloaded, as there was no lost data — the receiver will typically stop the sender before it loses any data. Thus, the sender will continue to speed up at an exponential rate; because it didn’t see any lost data, it will send data twice as fast as before! Because the receiver has a permanent speed disadvantage, this will require the receiver to send out pause frames twice as often. Things start snowballing until the receiver pauses the sender so often that the sender starts dropping its own data before it sends it, and thus finally sees some data being lost and slows down.

Is this a problem? In some ways it isn’t. Because TCP is a reliable protocol, nothing is ever really “lost”; it is simply retransmitted and life goes on. Ethernet flow control accomplishes the same thing as TCP flow control in this situation, as they both slow down the data transmission to the speed that the slower device can handle. There are some arguments to be made for there being an awkward overlap between the two flow control mechanisms, but it could be worse.

Unfortunately, it does get worse.

Head-of-line blocking

In the last example, I considered the case where two computers were directly connected to each other. This example is too simplistic to be of much use — when was the last time you saw two directly connected computers? It is a bit of a rarity. Let’s now look at what happens when you introduce a switch into the mix. For our purposes, let us assume that the switch fully supports Ethernet flow control and that it is willing to use it. Our new setup will consist of two desktop computers and one file server, all of which are attached to the switch. It isn’t any fun to make everything perfect, so let’s also say that one of the desktops has a 10 Mbps connection to the switch while the other desktop and the server have 100 Mbps connections.

This setup is usually fine — the 10 Mbps connection will be slower than the others, but it doesn’t cause too many problems, just slower service to the one desktop. Things could get ugly, though, if Ethernet flow control is enabled on the switch. Imagine that the 10 Mbps desktop requests a large file from the file server. The file server begins to send the file to the desktop initially at a slow rate, but quickly picks up steam. Eventually, the file server will start to send data to the desktop at 11 Mbps, which is more than the poor 10 Mbps connection can handle. Without flow control enabled on the switch, the switch would start to simply drop data segments destined to the desktop, which the file server would notice and start to throttle back its sending rate.

With flow control enabled on the switch, though, the switch takes a very different approach; it will send out its own pause frames to any port that is sending data to the now-overloaded 10 Mbps port. This means that the file server will receive a pause frame from the switch, requesting it to cease all transmissions for a certain amount of time. Is this a problem? Yes! Because pause frames cease all transmissions on the link, any other data that the file server is sending will be paused as well, including data that may be destined to the 100 Mbps desktop computer. Eventually the pause will expire and the file server will continue sending out data. Unfortunately, the TCP mechanism on the file server will not know that anything is wrong and will continue sending out data at faster and faster speeds, thus overloading the 10 Mbps desktop again. As before, the cycle will keep repeating itself until the file server starts dropping its own data. Unlike the previous situation, the innocent 100 Mbps desktop bystander is penalized and will see its transfers from the file server drop to 10 Mbps speeds.

This situation is called head-of-line blocking, and it is the major reason why Ethernet flow control is somewhat dangerous to use. When enabled on network switches, it can create situations where one slow link in a network can bring the rest of the network to a crawl. It gets especially bad if the backbones in your network have flow control enabled; it should be obvious by this point just how bad that could get.

When to enable flow control

So what should you do? Should you completely disable flow control on all computers and switches? Not necessarily. It is generally safe to leave flow control enabled on computers. Switches, though, should either have flow control disabled or configured such that they will honor received pause frames but will never send out new pause frames. Some Cisco switches are even permanently configured this way — they can receive pause frames but never emit them. To be honest, the complete answer to flow control is somewhat more complicated than this (e.g. you could probably enable pause frame emission if a switch port is connected to a slow backplane), but the safest bet is to disable flow control when given the option.

ref: http://virtualthreads.blogspot.com/2006/02/beware-ethernet-flow-control.html

Switch to mobile version
Advertisment ad adsense adlogger