Simple clock on PIC12F683 - three pins, two I2C buses - DS1307 + FD650
TL;DR
- A tiny clock uses a PIC12F683, a recycled FD650 four-digit 7-segment module, and a DS1307 RTC.
- Because the FD650 ignores normal I2C addressing, the design splits the bus into two SDA lines with a shared SCL and separate bit-banged I2C functions.
- The PIC12F683 has only 128 bytes of RAM, and the FD650 display traffic includes 0x11 at address 0x48 plus digit writes at 0x68, 0x6A, 0x6C, 0x6E.
- The clock works, time is counted down and displayed, and logic-analyzer captures confirm clean DS1307 reads and display transactions.
- Memory pressure forced the code to be rewritten with functions moved into the main loop and variables reduced, showing how tight the PIC12F683 budget is.
Generated by the language model.
Today we are building a tiny clock with time keeping, we will realise it based on an 8-bit PIC12F683 microcontroller with only 128 bytes of RAM, a 7-segment display module with FD650 controller recovered from electro-junk and a DS1307 RTC clock. I will also show full waveforms from I2C communication captured with a logic analyser in the theme.
Why not one I2C bus?
Let's start with the most important design problem and the obvious question that probably occurs to everyone after reading the introduction to this topic - why two I2C buses at all? Is it not possible to use addresses?
Unfortunately, it so happens that a large number of popular Chinese chips do not fully adhere to the I2C standard and do not support addressing - there, the first byte sent is not the address of the individual chip, but e.g. a command or immediately the address of the register on which we operate.
There is a similar problem with the FD650, on which I based this design. The FD650 is a clone of the TM1650, it also appears under the name HD2015. I have already described it extensively in a topic:
Running the HD2015 display/button controller after reverse engineering, comparison with TM1637
The FD650 has precisely this affliction, that it will interfere with our other addresses on the bus.
The other element used in the project, the DS1307, is fully I2C-compatible and doesn't do the problem, but so what?
The bus needs to be split - I decided to have two SDA data lines and a common clock SCL .
Organisation of work
The project is quite complex, so for simplicity I did the first stages on an Arduino. This was primarily dictated by the desire to check that my module from the FD650 survived the operation. Only after I was sure that everything was ok with it would I transfer the code to the PIC.
Module from FD650
I recovered the module from an old terrestrial TV receiver. The equipment had no remote control and no longer supported the newer standards with DVB-T2. I've been receiving this type of equipment quite often for a while now, so I recover parts from them in bulk. I didn't have room for another board, so I took the liberty of cutting out only what I could use:
In addition to the four-digit 7-segment display, there's its controller, FD650, and some circuitry from RF (unnecessary here - to be removed).
The circuit on the board may seem incomplete, but nothing could be further from the truth. The FD650 really needs a minimum of few components to operate. Below is a sample application of it, which is pretty much in line with what I have on the board. I have only removed the buttons.
As you can see, you don't even need many resistors - just the 2 kΩ one from the buttons. The LED display doesn't need extra resistors at all.
I2C scan from FD650
You don't have to take my word for it though. A simple I2C scan is easy to run, just poll consecutive addresses and check the ACK signal. I implemented this on an Arduino for convenience. I didn't even need to give an external pull up resistor (pulling up to the power supply), as the Arduino allows you to configure the pin in this mode. The polling is done in a loop and the active ones are written out to the UART:
Code: C / C++
There is only FD650 on the bus, but lots of addresses are reported. As feared.
For verification I disconnected one of the lines - communication is immediately broken:
First segments
This paragraph will be kept to a minimum as I have essentially repeated this 1:1 work from the previous presentation:
Running the HD2015 display/button controller after reverse engineering, comparison with TM1637
Everything went straight away and there was no problem with the segments or font. It looks like the segment-to-pin mapping is standardised here, so that most FD650-based tuners (or devices there?) and clones can share the same operating code.
Code: C / C++
Simple clock
I then implemented the showTime function, which simply splits the two numeric values into ones and tens, and then sets them to their respective positions and sends them to the display. I don't operate on a string (character string) here, because I would have to manually shift the number and add 0 for the minutes and hours values from 0 to 9.
Code: C / C++
Works:
Port to PIC12F
It is time to move the program to the PIC12F. The logic will remain the same, the pin operations will change. On the PIC we directly perform this on the registers. In addition, I set up an internal oscillator for it, such is enough, I have other plans for timing anyway.
Code: C / C++
I don't think these delay_ms are even recalculated to the current oscillator configuration and are skewing, but that's not a problem, there shouldn't be this kind of blocking expectation in the final product anyway. We'll use something better for this.
Let's focus on the communication itself over I2C for now. It's worth taking a look at what's happening on the bus. I used a logic state analyser for this, I have already presented it in a separate topic:
Salae 24MHz logic state analyser for £40 - analysis of unknown LED display protocol
I also added an I2C decoder to the collected data to see the bytes being transferred.
First, the whole transactions - you can see how the data is sent every (say) 250 ms on the display:
When zoomed in we see a single operation showTime , this consists of a set of operations to write data to the FD650.
Here we have, in sequence, the activation of the display (writing 0x11 to address 0x48) and the writing of four characters (addresses 0x68, 0x6A, 0x6C, 0x6E). The last operation is to re-enable the display - redundant, it was left over from my experiments and I forgot to remove it for the pictures.
An example close-up of the display enable operation:
Similarly, character notation:
Integrating DS1307
Now it is time to introduce the DS1307. The DS1307 is a so-called RTC (Real Time Clock), a real-time clock chip that is used to maintain the current date and time, even when the microcontroller is switched off or reset.
By using an external backup battery (usually CR2032), the DS1307 can operate independently of the main circuit power supply. Communication with the microcontroller is via I2C.
My module with the DS1307 still has an EEPROM on board, but I will skip it here for now.
The whole problem here is that I can't put the DS on the same bus as the FD650, because the FD650 doesn't support addressing in principle and takes up all addresses - I showed this in the paragraph "I2C scan with FD650".
For this reason I have introduced dividing the bus , as announced.
Now:
- SDA of the display is pin 0
- sCL common is pin 2
- SDA of the clock is pin 1
To save memory, I broke the DRY (Don't Repeat Yourself) rule and prepared two sets of I2C functions - one for pin 0, the other for pin 1.
Code: C / C++
The pull up resistors turned out to be unnecessary again - the ones from the PIC are sufficient. The code shown works - the time is counted down and displayed. Previously the RTC had to be programmed once, but for now let's assume we did it manually, separately....
The pictures show the time in mm:ss format, so it may seem odd, but it's more convenient to test this way.
What does it look like under the hood? I have connected a logic analyser to the D0 (clock), D1 (SDA first) and D2 (SDA second) lines. I have added I2C decoders.
When zoomed in you can see the correctly decoded transactions. No problems with start/stop conditions, no "ghosts" on the line:
This is what the time reading from the DS1307 looks like. You can see the characteristic address 0x68 (after shifting and adding the read/write bit 0xD0 or 0xD1) and reading from registers 0, 1, and 2, the value 00:28:50.
Similarly, the display control. Here you can see the sent segments of the individual digits.
Key reading
The next step is to read the keys. I don't have any keys on my board, so I added two according to the schematic earlier in the post. I then implemented reading them - reading via I2C from address 0x27. The key code is then returned, as well as a flag as to whether it is/was pressed.
Code: C / C++
As a test, I display this code on the display in hexadecimal format. By the way, I added characters 10-15 to the character map.
Code: C / C++
The reading can be seen on the analyser:
This makes it easy to identify the codes of the keys you have:
Limits, everywhere limits
At this stage the PIC's memory limits began to kick in:
pic12f683_clockI2C.asm:1877:Message[1304] Page selection not needed for this device. No code generated.
message: Using default linker script "C:\Program Files (x86)\gputils\lkr\12f683_g.lkr".
warning: Relocation symbol "_cinit" [0x0000] has no section. (pass 0)
error: No target memory available for section "IDD_idata_0_i".
error: Error while writing hex file.
I reworked and rewrote the program to put the functions in the main loop where possible and reduce the number of variables.
Finalize the clock
The final step is to add the hour and minute setting. These two buttons are sufficient for this. I have assumed that:
- one just toggles the mode (operation, edit hour, edit minute)
- the other increases the selected value, a single press increases it once and holding down the key increases it continuously by 1 as long as the key is held
To do this, I read the keys every loop, examine the current mode (work, edit hour, edit minute), in the case of editing I flash the selected digits and handle the key events accordingly. After editing, the time is saved.
Code: C / C++
Result:
Case combinations
I have also started making the enclosure, but that will be in a separate topic.
Summary
The project proved to be a very interesting and unusual experience. I've run the FD650 before and it wasn't a problem, but here I practiced pin saving (this PIC12F has little free IO, and I may still want to add an alarm soon, an ADC would be useful too) and then general optimisation. In the end, I managed to fit the whole thing on a small PIC and a functional clock came out programmatically.
Now the hardware can still be worked on - the board shown here could be made smaller, probably by cutting off unnecessary parts of the laminate, and then printing the case on a 3D printer and placing the buttons in a clever way.
Perhaps I will deal with this in a separate section.
I will attach a version of the design for SDCC soon.
Comments