Category Archives: Languages

Digital Sawtooth Signal Generation with FPGA

Hello,

On my previous post, I explained how to create a sawtooth signal at 10kHz with a microcontroller (MSP430). Since it gave a discrete increase of the ramp due to low number of steps, 128 steps in that case, I tried to create a signal with higher frequency and higher number of steps accordingly.

For this purpose, I used a Nexys3 FPGA board which has a Spartan 6 Xilinx chip on it. Internal clock of this board is 100MHz, but to reach this speed from DIO pins, VHDC expansion connector must be used. There was no available connector for this interface, so I tried my best from Pmod connectors.

You can find the theoretical part from the the previous post, so I will skip that and directly explain the implementation. I used different from the previous version, I used TI DAC902U, which is essentially the same but has 12-bit parallel input capacity. The external connections are also the same.

The following figure shows the best available clock signal I could obtain from the Pmod connectors. It is not like a square wave, but works fine for my purpose. The frequency is 12.5 MHz, and a good point is that at least it is highly accurate and stable even though shape is not so good.

FPGA Clock Signal Output at 12.5 MHz

FPGA Clock Signal Output at 12.5 MHz

In the following figure you can see the connections between the FPGA Board and the DAC.

Nexys3 - DAC Connections

Nexys3 – DAC Connections

Verilog code for this counter is pretty simple. I have done it in behavioral style to reduce the work required. Moreover, there would not be any performance difference in such a small scale hardware description.

module Counter(
   input Clk,
   output [13:0] Count
   );

reg [13:0] Count;

   always@(negedge Clk) begin
      if(Count == 9999) Count = 0;
      Count = Count + 1;
   end

endmodule

Instead of creating a frequency divider and a counter separately (They are the same thing eventually.)  I built a 14-bit counter and used 12 most significant bits of it. 11 of those are the clock count from 0 to 1249 and the least significant one is the clock signal supply of the DAC.

Results:

Sawtooth Signal Output

Sawtooth Signal Output

A Closer Look to the Falling Edge

A Closer Look to the Falling Edge

There is an obvious improvement on both rising and falling edges of the signal. The rise is much smoother and the fall does not have that short peak to the negative voltages.

As always, you can contact me about anything related or unrelated to the post.

Thank you for your visit in my blog.

Gunay TURAN

p.s. I Have found such a contact form in the WordPress interface. Hope it will make it easier to contact me 🙂 I will add one of those to each post…

 

 

 

Digital Sawtooth Signal Generation with MSP430

Hello,

Sawtooth signals have various applications for example FMCW radars, audio applications, CRT screens, etc. By using an operational amplifier, a voltage comparator, and a transistor switch you can create your own sawtooth signal. The idea behind this analog circuit is to charging a capacitor linearly and rapidly discharging it. However, it is pretty difficult to get a rapid falling edge from an analog circuit, so the circuit with those component creates an asymmetrical  triangular wave.

Analog Sawtooth Signal at 10 kHz

Analog Sawtooth Signal at 10 kHz

To overcome this problem, you can create a digital sawtooth generator that falls to zero (or to the offset you set) in no time. What you need is a digital-to-analog converter (DAC) and a controlling unit, in this case an MSP430G2553. Most DAC ICs use serial SPI or I2C interface and this reduces the speed dramatically because you are sending whole DAC count bit by bit. What if you need a sawtooth signal around a MHz, or more? For this purpose, there are parallel input high-speed DAC ICs which can give update rates up to 2.5 GSPS. In this application, I used a 10-bit 165 MSPS digital to analog converter, DAC900, to produce a 10 kHz sawtooth signal.

The signal is continuously increasing, and then roll back to zero. Therefore, it is easy to figure out what kind of parallel input to give to the DAC, a binary counter. When it reaches its maximum value, it rolls back to zero. Now, we need to calculate the maximum number of steps to fit in 100us (10kHz).

MSP430G2553 has a DCO at maximum of 16MHz, but this is the speed of the CPU clock, not the frequency of counting, since each instruction takes several cpu cycles, effective counting frequency will be reduced. One other point is that C is a high level language and its instructions take longer than Assembly instructions. Therefore, coding with Assembly will be a more effective solution.  Let us first calculate the counting frequency with full resolution, 10-bits. For this, we need to use an 11-bit counter so we can use the LSB as the DAC clock. If use the LSB as the clock, its period will determine our counting frequency.

10 bit resolution means 1024 DAC steps. If we divide 100us to 1024 we will get 97ns counting/clock period, requiring a bit more than 10MHz. This is unfortunately is not possible to get as a clock frequency because to count with two ports (since one port has 8 DIO pins, we need two of them to make a 11-bit counter), two nested loops and a comparison function is required. This will load the CPU and slow it down. I tried this method, and got maximum of 822kHz of counting frequency. Then how about counting with this frequency, and reducing the number of steps? 822kHz gives us 1.22us of count period, which means 90 steps for 100us! If we used only one port, we would have 8-bit counter and 7-bit resolution, giving us 128 steps. In that case I got approximately 1.6MHz count, which results in 80us to reach to the top value with 128 steps. This is not a big problem because we can slow down our DCO and get the required frequency. In order to use 128 steps to get 100us period, required clock frequency is 1.28MHz (Not CPU clock but the DAC clock). This value can be reached by appropriate DCO setting, which you will see in the following Assembly code[1].

 

;-------------------------------------------------------------------------------
; MSP430 Assembler Code Template for use with TI Code Composer Studio;
;-------------------------------------------------------------------------------
            .cdecls C,LIST,"msp430.h"       ; Include device header file

;-------------------------------------------------------------------------------
            .text                           ; Assemble into program memory
            .retain                         ; Override ELF conditional linking
                                            ; and retain current section
            .retainrefs                     ; Additionally retain any sections
                                            ; that have references to current
                                            ; section
;-------------------------------------------------------------------------------
RESET       mov.w   #__STACK_END,SP         ; Initialize stackpointer
StopWDT     mov.w   #WDTPW|WDTHOLD,&WDTCTL  ; Stop watchdog timer

;-------------------------------------------------------------------------------
                                            ; Main loop here
;-------------------------------------------------------------------------------
;DCO settings to ~1.28MHz
DcoSetng	clr.b 	&DCOCTL ; Select lowest DCOx
			bis.b 	#RSEL3+RSEL2+RSEL1+RSEL0,&BCSCTL1
			bis.b 	#DCO2,&DCOCTL 

SetupP1     bis.b   #255,&P1DIR
Mainloop    inc     &P1OUT
            jmp     Mainloop

;-------------------------------------------------------------------------------
;           Stack Pointer definition
;-------------------------------------------------------------------------------
            .global __STACK_END
            .sect 	.stack

;-------------------------------------------------------------------------------
;           Interrupt Vectors
;-------------------------------------------------------------------------------
            .sect   ".reset"                ; MSP430 RESET Vector
            .short  RESET

With this settings our counter is ready to use and the following figure is the capture of the clock output:

Clock output from MSP430

Clock output from MSP430

Having our counter, now we need to connect it to the DAC. I directly used the basic connection instructions given in the datasheet of the DAC900. To read a value from oscilloscope, I connected a 100 ohm resistor between the current output pin and ground.

DAC900 Connections [2]

DAC900 Connections [2]

Here is the realization of this circuit on breadboard:

MSP430 and DAC900 Connections

MSP430 and DAC900 Connections

DAC900 does not come with DIP packages, so I got one SOIC-28, and soldered it on my homemade SOIC-to-DIP converter circuit. You can buy one of those coverters. I must admit that I am quite shocked about the price because similar products cost $2 in Turkey…

In the following two figures you can see the results:

Sawtooth Signal Obtained from the DAC

Sawtooth Signal Obtained from the DAC

 

A Close-up to the Falling Edge of the Sawtooth

A Close-up to the Falling Edge of the Sawtooth

You can see the rising is noisy due to the long thin cables, but a more important problem is that there isn’t the smooth increase of the ramp as I got in the analog circuit. However, the greatest advantage of this circuit is the real rapid falling edge.

To solve the problem with discrete increase of the ramp, I used an FPGA board, and created a sawtooth with 1250 steps. It gave much better results, and I will post that later…

Please feel free to contact me about any problems, suggestions, etc…

Thank you

Gunay TURAN

 

References:

[1] This code is taken from TI’s example codes for CCS and modified.

[2] http://www.ti.com/lit/ds/symlink/dac900.pdf

 

 

Random Number Generator with ATmega16

Hello,

In this article I am going to explain how we built a random number generator with an LCD display on ATmega16 for our microprocessors course group project.

This is how the finished project looks like:

1. Properties

The random number generator can generate integers in a user defined interval of limited by the absoulte interval of [0, 999].

The user can choose a value in the interval which won’t be generated.

Control is done by a 4×4 matrix keyboard.

Result and user interface is displayed on an LCD screen.

A DC buzzer is used to show button press.

2. Theory

To generate the random number, instead of random number algorithm we used human insensitivity for our own good. We used a 8-bit timer counter working at 16 MHz, which will scan whole interval in 16 microseconds. As the controller is powered up timer will start counting and the user cannot possibly follow the track of the timer. When all the parameters are set, the MCU scales the 8-bit timer counter value in the interval of [0, 255] to the user defined interval. If the generated value is the same as the defined unwanted number it creates another number immidiately.

3. Software

     a. Main Function:

In the main function register definitions, LCD initialization, and interrupt enabling are done first. Then welcome screen is generated on the display.


// main funciton
int main(){

// definition of digits entered by the user
int dgt0,dgt1,dgt2,dgt3,dgt4,dgt5,dgt6,dgt7,dgt8;

// port direction definitions
DDRB = 0b11110000;
DDRC = 0b11111111;

// portc initialization
PORTC = 0;

// lcd initialization
lcd_init();
lcd_clrscr();

// timer zero configuration without prescaling
TCCR0 |= (0 << CS02) | (0 << CS01) | (1 << CS00);

// lcd welcome screen
lcd_puts("CMPT PROJECT2 ");
lcd_puts("RANDOM GENERATOR");
lcd_puts("Burak Gunay");
lcd_puts("Kerem Serkan");

// welcome screen delay of 2 seconds
_delay_ms(2000);

// user interface screen
lcd_puts("EnterNumber1: ");
lcd_puts("EnterNumber2: ");
lcd_puts("Unwanted Number?");
lcd_puts("A:Yes/B:No : ");

In the while loop at the and of the function, input processing is done via the control of global button flag. This flag is used show which number is entered and which one is expected. The first inner while loop waits until a button is pressed. Then it calls input_check function and assings the return value to the first digit, dgt0.

After that the digit obtained is displayed and a buzz sound iz generated via beep function. At last the flag is incremented by one, therefore following while loop is started to be executed.


// input processing
while(button_flag != -1){
while(button_flag == 0){
dgt0 = input_check();
screen_display(dgt0);
beep();
button_flag++;
}
while(button_flag == 1){
dgt1 = input_check();
screen_display(dgt1);
beep();
button_flag++;
}
while(button_flag == 2){
dgt2 = input_check();
screen_display(dgt2);
beep();
button_flag++;
}
while(button_flag == 3){
dgt3 = input_check();
screen_display(dgt3);
beep();
button_flag++;
}
while(button_flag == 4){
dgt4 = input_check();
screen_display(dgt4);
beep();
button_flag++;
}
while(button_flag == 5){
dgt5 = input_check();
screen_display(dgt5);
beep();
button_flag++;
}
while(button_flag == 6){
button_flag = input_check();
beep();
}
while(button_flag == 10){
screen_display(0);
beep();
button_flag = 7;
}
while(button_flag == 11){
screen_display(0);
beep();
random_generator(dgt0,dgt1,dgt2,dgt3,dgt4,dgt5,-1,-1,-1);
button_flag = -1;
}
while(button_flag == 7){
dgt6 = input_check();
screen_display(dgt6);
beep();
button_flag++;
}
while(button_flag == 8){
dgt7 = input_check();
screen_display(dgt7);
beep();
button_flag++;
}
while(button_flag == 9){
dgt8 = input_check();
screen_display(dgt8);
beep();
random_generator(dgt0,dgt1,dgt2,dgt3,dgt4,dgt5,dgt6,dgt7,dgt8);
button_flag = -1;
}
}

When button_flag reaches 6, it takes the last digit of the second value of the interval and user makes a choice to have an unwanted number or not. If user chooses not to have it, flag is set to 10, and the number generation is started. Otherwise, program waits for three more digits and creates the number with the exception of the given unwanted number.

     b. Input Control

Numeric input is given by a 4×4 matrix keyboard. This is a resistive matrix which is controlled by eight digital I/O pins. The four column pins are output of the MCU and the remaining four row pins are input to the MCU. To control it we give 4-bit binary numbers which contain only one high bit to the columns and check the input at the row pins. If a digital low occurs on the row pins which means the button at the intersection of the row which had low and the column which had digital high is pressed. Therefore we continuously change the ‘active’ column and wait for a zero at the row pins.

4x4 Matrix Keyboard Schematics(1)

4×4 Matrix Keyboard Schematics [1]

And this is the C code for taking the inputs. There is an unclear point which will be discussed at the section Encountered Problems.


// input control function
int input_check(){
while(1){
// portb 7~4 are used as test variables
// input ports are set to 1 to enable pull-ups
PORTB = 0b01111111;
if(bit_is_clear(PINB,3)) return 10;
if(bit_is_clear(PINB,2)) return 4;
if(bit_is_clear(PINB,1)) return 7;
PORTB = 0b10111111;
if(bit_is_clear(PINB,3)) return 1;
if(bit_is_clear(PINB,2)) return 5;
if(bit_is_clear(PINB,1)) return 8;
if(bit_is_clear(PINB,0)) return 0;
PORTB = 0b11011111;
if(bit_is_clear(PINB,3)) return 2;
if(bit_is_clear(PINB,2)) return 6;
if(bit_is_clear(PINB,1)) return 9;
PORTB = 0b11101111;
if(bit_is_clear(PINB,3)) return 3;
if(bit_is_clear(PINB,2)) return 11;
}
}

     c. Display

In this project we used a 4×16 LCD screen and controlled it with lcd_h.h library. Since this part is quite easy to, I won’t get into detail and just will share the corresponding C code.


// lcd display function
void screen_display(int dgt){

if(button_flag == 0){
lcd_gotoxy(13,0); // setting lcd cursor
lcd_putc(dgt+48); // int + 48 = ascii code of that integer
}
if(button_flag == 1){
lcd_gotoxy(14,0);
lcd_putc(dgt+48);
}
if(button_flag == 2){
lcd_gotoxy(15,0);
lcd_putc(dgt+48);
}
if(button_flag == 3){
lcd_gotoxy(13,1);
lcd_putc(dgt+48);
}
if(button_flag == 4){
lcd_gotoxy(14,1);
lcd_putc(dgt+48);
}
if(button_flag == 5){
lcd_gotoxy(15,1);
lcd_putc(dgt+48);
}
if(button_flag == 10){
lcd_gotoxy(0,3);
lcd_puts("Yes|Number :");
}
if(button_flag == 11){
lcd_gotoxy(0,3);
lcd_puts("No ");
}
if(button_flag == 7){
lcd_gotoxy(13,3);
lcd_putc(dgt+48);
}
if(button_flag == 8){
lcd_gotoxy(14,3);
lcd_putc(dgt+48);
}
if(button_flag == 9){
lcd_gotoxy(15,3);
lcd_putc(dgt+48);
}
}

// lcd displat of result
void result_display(int LO_V, int HI_V, int UW_V, int FI_V){

char buffer[16]; // lcd line buffer

// lcd clear
lcd_clrscr();
lcd_gotoxy(0,0);

// result display
lcd_puts("A Random Number");
lcd_gotoxy(0,1);
sprintf(buffer,"Between %d-%d",LO_V,HI_V);
lcd_puts(buffer);

// check for unwanted number
if(UW_V != -111){
lcd_gotoxy(0,2);
sprintf(buffer,"Excluding %d",UW_V);
lcd_puts(buffer);
}

lcd_gotoxy(0,3);
sprintf(buffer,"Result %d",FI_V);
lcd_puts(buffer);
}

     d. Random Generation

As I explained in the Theory section, we used an 8-bit timer counter, got its current value, and scaled it to the given interval. This part only consists obtaining timer value and scaling it, which is a simple proporiton operation. Also we added some control statement to check if we generated the unwanted number or not. If so, the control statement is executed and generates a new random number immediately.


// random number generation function
void random_generator(int low1,int low2,int low3,int high1,int high2,int high3,int uw1,int uw2,int uw3){

// converting seperate digits into actual numbers
int low_limit;
low_limit = low1*100 + low2*10 + low3;

int high_limit;
high_limit = high1*100 + high2*10 + high3;

int unwanted_value;
unwanted_value = uw1*100 + uw2*10 + uw3;

// reading current timer0 value to create random number
float timer_value;
timer_value = TCNT0;

int temp_result;
int random_result;

// mapping the timer value to given interval
temp_result = (float)((timer_value/255)*(high_limit - low_limit)) + low_limit;

// cropping the fraction part of float
random_result = temp_result;

// regeneration of the number in the case of resulting unwanted number
while(random_result == unwanted_value){
timer_value = TCNT0;
temp_result = (float)((timer_value/255)*(high_limit - low_limit)) + low_limit;
}

random_result = temp_result;

// calling the final screen animation
final_animation();

// sending the result to the display function
result_display(low_limit,high_limit,unwanted_value,random_result);
}

     e. Beep

Since we used a DC buzzer, this is the simplest part of the code. We just change the input voltage from 0 to 5V with a desired delay value. The most important concern in this part is the delay time, because during the beep MCU cannot do another execution, and if you make it too long your next button press during the beep will not be read.


// beep function to indicate button press
void beep(){
PORTC = 128;
_delay_ms(500);
PORTC = 0;
}

     f. Final Animation – Loading Screen

We did this small final animation just because we can. During this process no other execution is done, so it has no essence for the project.


void final_animation(){

int i;

// lcd line buffer
char buffer[16];

// percentage counter
int counter=0;

// loading screen
lcd_clrscr();
lcd_puts("LOADING PLEASE WAIT ");
_delay_ms(500);
lcd_gotoxy(0,2);
lcd_putc('[');
lcd_gotoxy(9,2);
lcd_putc(']');

// definition of a new char
lcd_command ( 0b01000000 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;

// animation
for(i = 0; i<=100; i++){
_delay_ms(30);
lcd_gotoxy(12,2);
sprintf(buffer,"%3.d%%",i);
lcd_puts(buffer);
if(i == 10){
lcd_gotoxy(0,2);
lcd_putc(0);
}
if(i==20){
lcd_gotoxy(1,2);
lcd_putc(0);
}
if(i==30){
lcd_gotoxy(2,2);
lcd_putc(0);
}
if(i==40){
lcd_gotoxy(3,2);
lcd_putc(0);
}
if(i==50){
lcd_gotoxy(4,2);
lcd_putc(0);
}
if(i==60){
lcd_gotoxy(5,2);
lcd_putc(0);
}
if(i==70){
lcd_gotoxy(6,2);
lcd_putc(0);
}
if(i==80){
lcd_gotoxy(7,2);
lcd_putc(0);
}
if(i==90){
lcd_gotoxy(8,2);
lcd_putc(0);
}
if(i==100){
lcd_gotoxy(9,2);
lcd_putc(0);
}
}
}

 g. The Complete Code


/**
* VUT Brno FEKT - CMPT Project 2
* Random Number Generator
*
* Authors: Burak Dalogullari, Gunay Turan, Kerem Sennik, Serkan Ozatik
*
* Description: Random Number Generator which has an LCD user interface
* takes an interval between 0 ~ 999 from user with possibility of an
* unwanted number in this specified interval, and returns a random number
* on that interwal, excluding the unwanted number if there is any.
*
*/

#include // include definition file for Atmel MCU
#include // standard input and output library (needed for sprintf)
#include "lcd_h.h" // include library for LCD display
#define F_CPU 16000000UL // specify clock frequency (needed for delay library)
#include // include delay library

int button_flag = 0; // flag used for specifying the input

// main funciton
int main(){

// definition of digits entered by the user
int dgt0,dgt1,dgt2,dgt3,dgt4,dgt5,dgt6,dgt7,dgt8;

// port direction definitions
DDRB = 0b11110000;
DDRC = 0b11111111;

// portc initialization
PORTC = 0;

// lcd initialization
lcd_init();
lcd_clrscr();

// timer zero configuration without prescaling
TCCR0 |= (0 << CS02) | (0 << CS01) | (1 << CS00);

// lcd welcome screen
lcd_puts("CMPT PROJECT2 ");
lcd_puts("RANDOM GENERATOR");
lcd_puts("Burak Gunay");
lcd_puts("Kerem Serkan");

// welcome screen delay of 2 seconds
_delay_ms(2000);

// user interface screen
lcd_puts("EnterNumber1: ");
lcd_puts("EnterNumber2: ");
lcd_puts("Unwanted Number?");
lcd_puts("A:Yes/B:No : ");

// input processing
while(button_flag != -1){
while(button_flag == 0){
dgt0 = input_check();
screen_display(dgt0);
beep();
button_flag++;
}
while(button_flag == 1){
dgt1 = input_check();
screen_display(dgt1);
beep();
button_flag++;
}
while(button_flag == 2){
dgt2 = input_check();
screen_display(dgt2);
beep();
button_flag++;
}
while(button_flag == 3){
dgt3 = input_check();
screen_display(dgt3);
beep();
button_flag++;
}
while(button_flag == 4){
dgt4 = input_check();
screen_display(dgt4);
beep();
button_flag++;
}
while(button_flag == 5){
dgt5 = input_check();
screen_display(dgt5);
beep();
button_flag++;
}
while(button_flag == 6){
button_flag = input_check();
beep();
}
while(button_flag == 10){
screen_display(0);
beep();
button_flag = 7;
}
while(button_flag == 11){
screen_display(0);
beep();
random_generator(dgt0,dgt1,dgt2,dgt3,dgt4,dgt5,-1,-1,-1);
button_flag = -1;
}
while(button_flag == 7){
dgt6 = input_check();
screen_display(dgt6);
beep();
button_flag++;
}
while(button_flag == 8){
dgt7 = input_check();
screen_display(dgt7);
beep();
button_flag++;
}
while(button_flag == 9){
dgt8 = input_check();
screen_display(dgt8);
beep();
random_generator(dgt0,dgt1,dgt2,dgt3,dgt4,dgt5,dgt6,dgt7,dgt8);
button_flag = -1;
}
}
}

// input control function
int input_check(){
while(1){
// portb 7~4 are used as test variables
// input ports are set to 1 to enable pull-ups
PORTB = 0b01111111;
if(bit_is_clear(PINB,3)) return 10;
if(bit_is_clear(PINB,2)) return 4;
if(bit_is_clear(PINB,1)) return 7;
PORTB = 0b10111111;
if(bit_is_clear(PINB,3)) return 1;
if(bit_is_clear(PINB,2)) return 5;
if(bit_is_clear(PINB,1)) return 8;
if(bit_is_clear(PINB,0)) return 0;
PORTB = 0b11011111;
if(bit_is_clear(PINB,3)) return 2;
if(bit_is_clear(PINB,2)) return 6;
if(bit_is_clear(PINB,1)) return 9;
PORTB = 0b11101111;
if(bit_is_clear(PINB,3)) return 3;
if(bit_is_clear(PINB,2)) return 11;
}
}

// lcd display function
void screen_display(int dgt){

if(button_flag == 0){
lcd_gotoxy(13,0); // setting lcd cursor
lcd_putc(dgt+48); // int + 48 = ascii code of that integer
}
if(button_flag == 1){
lcd_gotoxy(14,0);
lcd_putc(dgt+48);
}
if(button_flag == 2){
lcd_gotoxy(15,0);
lcd_putc(dgt+48);
}
if(button_flag == 3){
lcd_gotoxy(13,1);
lcd_putc(dgt+48);
}
if(button_flag == 4){
lcd_gotoxy(14,1);
lcd_putc(dgt+48);
}
if(button_flag == 5){
lcd_gotoxy(15,1);
lcd_putc(dgt+48);
}
if(button_flag == 10){
lcd_gotoxy(0,3);
lcd_puts("Yes|Number :");
}
if(button_flag == 11){
lcd_gotoxy(0,3);
lcd_puts("No ");
}
if(button_flag == 7){
lcd_gotoxy(13,3);
lcd_putc(dgt+48);
}
if(button_flag == 8){
lcd_gotoxy(14,3);
lcd_putc(dgt+48);
}
if(button_flag == 9){
lcd_gotoxy(15,3);
lcd_putc(dgt+48);
}
}

// lcd displat of result
void result_display(int LO_V, int HI_V, int UW_V, int FI_V){

char buffer[16]; // lcd line buffer

// lcd clear
lcd_clrscr();
lcd_gotoxy(0,0);

// result display
lcd_puts("A Random Number");
lcd_gotoxy(0,1);
sprintf(buffer,"Between %d-%d",LO_V,HI_V);
lcd_puts(buffer);

// check for unwanted number
if(UW_V != -111){
lcd_gotoxy(0,2);
sprintf(buffer,"Excluding %d",UW_V);
lcd_puts(buffer);
}

lcd_gotoxy(0,3);
sprintf(buffer,"Result %d",FI_V);
lcd_puts(buffer);
}

// random number generation function
void random_generator(int low1,int low2,int low3,int high1,int high2,int high3,int uw1,int uw2,int uw3){

// converting seperate digits into actual numbers
int low_limit;
low_limit = low1*100 + low2*10 + low3;

int high_limit;
high_limit = high1*100 + high2*10 + high3;

int unwanted_value;
unwanted_value = uw1*100 + uw2*10 + uw3;

// reading current timer0 value to create random number
float timer_value;
timer_value = TCNT0;

int temp_result;
int random_result;

// mapping the timer value to given interval
temp_result = (float)((timer_value/255)*(high_limit - low_limit)) + low_limit;

// cropping the fraction part of float
random_result = temp_result;

// regeneration of the number in the case of resulting unwanted number
while(random_result == unwanted_value){
timer_value = TCNT0;
temp_result = (float)((timer_value/255)*(high_limit - low_limit)) + low_limit;
}

random_result = temp_result;

// calling the final screen animation
final_animation();

// sending the result to the display function
result_display(low_limit,high_limit,unwanted_value,random_result);
}

// beep function to indicate button press
void beep(){
PORTC = 128;
_delay_ms(500);
PORTC = 0;
}

void final_animation(){

int i;

// lcd line buffer
char buffer[16];

// percentage counter
int counter=0;

// loading screen
lcd_clrscr();
lcd_puts("LOADING PLEASE WAIT ");
_delay_ms(500);
lcd_gotoxy(0,2);
lcd_putc('[');
lcd_gotoxy(9,2);
lcd_putc(']');

// definition of a new char
lcd_command ( 0b01000000 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;
lcd_data ( 0b11111 ) ;

// animation
for(i = 0; i<=100; i++){
_delay_ms(30);
lcd_gotoxy(12,2);
sprintf(buffer,"%3.d%%",i);
lcd_puts(buffer);
if(i == 10){
lcd_gotoxy(0,2);
lcd_putc(0);
}
if(i==20){
lcd_gotoxy(1,2);
lcd_putc(0);
}
if(i==30){
lcd_gotoxy(2,2);
lcd_putc(0);
}
if(i==40){
lcd_gotoxy(3,2);
lcd_putc(0);
}
if(i==50){
lcd_gotoxy(4,2);
lcd_putc(0);
}
if(i==60){
lcd_gotoxy(5,2);
lcd_putc(0);
}
if(i==70){
lcd_gotoxy(6,2);
lcd_putc(0);
}
if(i==80){
lcd_gotoxy(7,2);
lcd_putc(0);
}
if(i==90){
lcd_gotoxy(8,2);
lcd_putc(0);
}
if(i==100){
lcd_gotoxy(9,2);
lcd_putc(0);
}
}
}

4. Encountered Problems

We had problems only at the input reading. First we kept taking garbage input. This is resulted from the lack of pull-up resistors in the development board we are using. This problem is solved by activating the MCU internal pull-ups, which can be setting the input bits in the PORTx register.

The second problem with the matrix keyboard is hardware related. Due to a misconnection on the first row, whenever we pressed 1, we got 2, whenever we pressed 2 we got 3, and so on. That’s why we changed the input check function in the C code to get the desired input.

5. The Team: Burak Daloğulları, Kerem Şennik, Serkan Özatik

We accomplished this project with the same group as we did the Kitchen Alarm Project. I appreciate their hard work during the project.

6. Exercise

Try to add a button which generates a new number with the given parameters.

If you have any problems, questions, comments, and/or objections please feel free to contact me.

Günay Turan

Source(s):

[1] The schematics is taken from: http://shree-electronics.com/interfacingkeyboard.html

 

POV Display for Bike Wheel with MSP430

Hi,

I finally completed my training project, and decided to make it open source. In this post I will explain all about the project and gave its source files. Hope it’ll be helpful for hobbyists working on microcontrollers.

Project Descripton:

POV Speedometer

POV Speedometer

This project is based on POV (Persistence of Vision) principle. Just by timing some moving LEDs in a predetermined manner, human eye can be decevied to see a contiuous image instead of moving lamps. On the light of this principle, I created a board having an array of 15 LEDs and placed it on a bicycle wheel. As the wheel turns, the board measures the speed and displays it according to the principle explained above. Different from the other POV display projects can be found online, this one does not require a motor to turn the LEDs but takes the movement from the bicycle. 

Theory:

Speed Measurement: Speed is measured not done by bicycle’s movements but revolution time of the wheel. I used a reed switch on the board and a stationary magnet placed accross the switch on bike. Whenever the switch passes the magnet, timer module of the MCU starts to count until the adjacent pass. Adding circumference of the wheel into account (The value is to be measured and written into the C code) we can easily caluclate the speed by dividing the circumference to the revolution time. This operation is done in every single revolution continuously. 

Timer: In this project I used an MSP430F2112 which has a timer on it. TimerA module of the MCU is a 16-bit counter with source clock options and interrupt capabilities. I set the DCO (Digitally Controlled Oscillator) of the MCU to work at 12MHz, divide it to 8, and made it source SMCLK (Sub-main Clock). Then, I divided it to 8 and sourced the TimerA module. Thus, I got a 16-bit counter working at (12M/8)/8 = 187.5kHz. It is quite simple to calculate the time having know al these. Since it takes 1/187500 seconds to counter increment once, dividing the total number of counts to 187500 will give us the time passed between two switching in terms of seconds. 

Display: We know the speed and revolution time, and now it comes to display the speed, covered distance or any message or shape you would like to see on the bicycle’s wheel (with low resolution and some disstortion due to the circular shape of the display of course). Here’s an Excel table to illustrate the principle of the POV display:

Illustration of POV Display on Excel

Illustration of POV Display on Excel

It is easy to understand: a column of the matrix is a position of the LED array and the numbers show the state of the each LED in that position -on or off. In the example shown in the figure above, assume LEDs are moving from right to left, in the first position only the bottommost LED is on and the others are of. Moving the second position, and third and then fourth LED states are the same, so we actually draw a line at the bottom of the display. Then the last seven LEDs are on on the fifth position, so on and so forth. Human eye is not able to recognize this process step by step and each revolution, no matter in which direction it sees the complete message as it is floating in the midair (after reaching some speed of course).

Hardware

Since the project will be placed on a turning wheel it cannot be done on Launcpad or supplied from a computer. Therefore, we need to create a mobile board. If you have enough equipment, I recommend you to print a PCB and use SMT (surface mount technology) components to reduce the size of the circuit significantly, or you can use a stripboard and through-hole components which are easier to use and requires less equipment. I built both and will share the requried components for each one.

PCB

  • Printed and drilled board (I assume you know how to do this with to be given .brd file)
  • MSP430F2112 28-pin TSSOP Package or any other MSP430 with the same package
  • 15 SMD single color LEDs (color and number are optional)
  • SMD resistors (603 size): 15x100Ohm, 1x10k, 1x47k (100 ohm resistors can be used in different values to match the LEDs’ specifications)
  • SMD capacitors (603 size): 2.2nF, 2.2uF, 100nF – one from each.
  • A reed switch
  • 2x Single AAA Battery holders (through-hole) and batteries
  • An on-off switch
  • A 4-pin male-male connector

Stripboard

  • A stripboard at enough sizes
  • MSP430G2553 20-pin PDIP Package or any other MSP430 with the same package
  • 7 single coloer through-hole LEDs (color and number are optional)
  • Resistors: 7x100Ohm, 1x10k, 1x47k (100 ohm resistors can be used in different values to match the LEDs’ specifications)
  • Capacitor: 100nF, 2.2uF
  • A reed switch
  • A 3V cell battery and its holder
  • An on-off switch
  • A 20-pin IC connector

Software

I used TI’s Code Composer Studio 5.3.0 for coding and debugging, and used Cadence for schematics and PCB design.

Here is the code to display 10 characters in two lines as shown in the Excel table illustrution above. You can change the display using the excel file to be provided, and I’ll explain how to use it in a readme file.

#include <msp430f2112.h>

#define SWITCH BIT0;  			// Reed Switch at P2.0

unsigned long PERIOD;			// Count of the timer between two switching
unsigned int COUNT = 0;			// To count timer overflows
volatile unsigned int Switch_Flag = 0;

// Put the arrays obtained from the excel table here
unsigned int P1ARRAY[25] = {254,130,130,130,124,0,130,254,130,0,76,146,146,146,100,254,18,18,18,12,254,128,128,128,128};
unsigned int P2ARRAY[25] = {0,4,0,4,0,30,2,2,2,4,12,18,18,18,12,6,8,24,8,6,0,4,0,4,0};
unsigned int P3ARRAY[25] = {2,4,4,4,2,7,2,2,2,1,7,0,0,0,7,3,6,0,6,3,2,4,4,4,2};

//delay function
void ms_delay_function(unsigned long T){
 unsigned int i;
 T /= 188;

	for(i=0;i<T;i++){
		__delay_cycles(12000);
	}
}

// This function displays the view obtained from the excel table
// User can comment out the blank cycle specified above to remove
// spaces between characters.
int display_function(){

int i = 0;

	  for(i=24;i>=0;i--){
		  P1OUT = P1ARRAY[i];
		  P2OUT = P2ARRAY[i];
		  P3OUT = P3ARRAY[i];
		  ms_delay_function(PERIOD/60);

		  // This puts space between characters
		  // Comment it out if not displaying a text
		  if(i%5 == 0){
			  P1OUT = 0x00;
		  	  P2OUT = 0x00;
		  	  P3OUT = 0x00;
			  ms_delay_function(PERIOD/60);
		  }

		  // If any switching occurs during the display
		  // returns the function and calculates new speed
		  if(Switch_Flag == 1){
		 	 		  return 0;
		 	 	  }
	  }

	  return 1;
}

int main(void)
{

  WDTCTL = WDTPW + WDTHOLD;     // Stop WDT

  BCSCTL1 = CALBC1_12MHZ;       // DCO -> 12MHz
  DCOCTL = CALDCO_12MHZ;

  BCSCTL2 |= DIVS_3; 		// SMCLK -> 1.5MHz

  // Port initialization
  P1SEL = 0x00;
  P1DIR = 0xFF;
  P2SEL = 0x00;
  P2DIR = 0xFE;
  P3SEL = 0x00;
  P3DIR = 0xFF;
  P3OUT = 0x07;
  P2OUT = 0x1E;
  P1OUT = 0xFF;

  TACCTL0 = CCIE;		            // CCR0 interrupt enabled
  TACCR0 = 0xFFFF;                          // CCR0 = 65535

  TACTL = TASSEL_2 | MC_1 | ID_3;           // SMCLK, up mode, 187.5 kHz

  P2IE |= SWITCH;			// P2.0 interrupt enabled
  P2IES = 1; 				// interrupt at falling edge
  P2IFG &= ~SWITCH;			// interrupt flag cleared

 __enable_interrupt(); 			// enable interrupts

  while(1){

    if(Switch_Flag == 1){

    	Switch_Flag = 0;			// Clears switch flag
    	ms_delay_function(PERIOD/5);		// Puts a delay between switch and display
    	display_function();			// Calls display function

    }
  }
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A0 (void)
{
COUNT++;				// Detects timer overflows to measure period
}

// Port 2 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
	Switch_Flag = 1;			// Switch flag is set
	PERIOD = COUNT*65535 + TAR;  		// Each overflow*0xFFFF + TAR = #clock cycles
	COUNT = 0;				// Counter reinitialization
	TAR = 0x00;				// Timer counter is reset
	__delay_cycles(60000);			// SW debouncer of 5ms
	__delay_cycles(60000);			// SW debouncer of 5ms
	P2IFG &= ~SWITCH;			// Interrupt flag cleared

}

// Trap ISR assignment - put all unused ISR vector here
#pragma vector = ADC10_VECTOR, NMI_VECTOR, PORT1_VECTOR, WDT_VECTOR, USCIAB0TX_VECTOR, \
		USCIAB0RX_VECTOR, COMPARATORA_VECTOR, TIMER0_A1_VECTOR, TIMER1_A1_VECTOR, TIMER1_A0_VECTOR
__interrupt void TrapIsr(void)
{
  //You can trap the CPU here but it is not necessary
}

And here is the code which displays the speed in km/h and total covered distance in meters. I explained how to use this in the readme file too.

#include <msp430f2112.h>

#define SWITCH BIT0;  			        // Reed Switch at P2.0
#define CIRCUMFERENCE 176		        // With a radius of 28cm

unsigned long PERIOD;				// Count of the timer between two switching
unsigned long dummy = 0;
unsigned int COUNT = 0;				// To count timer overflows
unsigned int REV_TIME;				// Time elapsed as kiloseconds
unsigned int SPEED = 0;				// Speed of the bike in km/h
unsigned int TURN = 0;				// Number of wheel revolutions
unsigned int DISTANCE = 0;			// Total distance in meters

volatile unsigned int Switch_Flag = 0;

//LED patterns for digits from 0 to 9 for each display line
const int TURNS_LINE_P1[10][5] = {{124,130,130,130,124},{0,0,0,0,254},{242,146,146,146,158},
		{0,146,146,146,254},{30,16,16,16,254},{158,146,146,146,242},{254,146,146,146,242},
		{0,2,2,2,254},{254,146,146,146,254},{158,146,146,146,254}};

const int SPEED_LINE_P2[10][5] = {{12,18,18,18,12},{0,0,0,0,30},{26,18,18,18,22},{0,18,18,18,30},
		{6,0,0,0,30},{22,18,18,18,26},{30,18,18,18,26},{0,2,2,2,30},{30,18,18,18,30},{22,18,18,18,30}};

const int SPEED_LINE_P3[10][5] = {{7,0,0,0,7},{0,0,0,0,7},{6,2,2,2,3},{0,2,2,2,7},{3,2,2,2,7},
		{3,2,2,2,6},{7,2,2,2,6},{0,0,0,0,7},{7,2,2,2,7},{3,2,2,2,7}};

//yeni delay function
void ms_delay_function(unsigned long T){
 unsigned int i;
 T /= 188;

	for(i=0;i<T;i++){
		__delay_cycles(12000);
	}
}

//This function displays digits by using the LED patterns
int display_digits(unsigned int up, unsigned int low){
	int i;

	for(i=4;i>=0;i--){
		P1OUT = TURNS_LINE_P1[low][i];
		P2OUT = SPEED_LINE_P2[up][i];
		P3OUT = SPEED_LINE_P3[up][i];
		ms_delay_function(PERIOD/60);
		if(Switch_Flag == 1){
	 		  return 0;
	    }
	}

	P1OUT = 0x00;
	P2OUT = 0x00;
	P3OUT = 0x00;
	ms_delay_function(PERIOD/60);
	return 1;
}

int main(void)
{

	  WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
	  BCSCTL1 = CALBC1_12MHZ;            	    // DCO -> 12MHz
	  DCOCTL = CALDCO_12MHZ;
	  BCSCTL2 |= DIVS_3;                        // SMCLK -> 1.5MHz

	  // Port initialization
	  P1SEL = 0x00;
	  P1DIR = 0xFF;
	  P2SEL = 0x00;
	  P2DIR = 0xFE;
	  P3SEL = 0x00;
	  P3DIR = 0xFF;
	  P3OUT = 0x07;
	  P2OUT = 0x1E;
	  P1OUT = 0xFF;

	  TACCTL0 = CCIE;			    // CCR0 interrupt enabled
	  TACCR0 = 0xFFFF;                          // CCR0 = 65535

	  TACTL = TASSEL_2 | MC_1 | ID_3;           // SMCLK, up mode, 187.5 kHz

	  P2IE |= SWITCH;			    // P1.0 interrupt enabled
	  P2IES = 1; 				    // interrupt at falling edge
	  P2IFG &= ~SWITCH;			    // interrupt flag cleared

	 __enable_interrupt(); 			    // enable interrupts

  while(1){

    if(Switch_Flag == 1){

    	Switch_Flag = 0;
    	ms_delay_function(PERIOD/5);

        //Calculation of digits from LSB to MSB and sending them to the display function
    	display_digits(SPEED%10,DISTANCE%10);
    	display_digits((SPEED%100)/10,(DISTANCE%100)/10);
    	display_digits((SPEED%1000)/100,(DISTANCE%1000)/100);
    	display_digits((SPEED/1000)%10,(DISTANCE/1000)%10);
    }
  }
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A0 (void)
{
COUNT++;					// Detects timer overflows to measure period
}

// Port 1 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
	Switch_Flag = 1;
	PERIOD = COUNT*65535 + TAR;  		 // Each overflow*0xFFFF + TAR = #clock cycles
	REV_TIME = PERIOD / 188;                 // Rev_time (kseconds)
	SPEED = 36*CIRCUMFERENCE / REV_TIME;     // Speed (km/h)
	TURN++;					 // Number of revolutions
	dummy = TURN*176;			 // Dummy long variable to increase maximum distance
	dummy /= 100;
	DISTANCE = dummy;			 // Distance in meters
	COUNT = 0;				 // Counter reinit
	TAR = 0x00;				 // Timer count cleared

	__delay_cycles(60000);			 // SW debouncer of 5ms
	__delay_cycles(60000);			 // SW debouncer of 5ms
	P2IFG &= ~SWITCH;			 // Interrupt flag cleared

}

// Empty ISR to prevent unvanted actions and it also removes CCS warnings
#pragma vector = ADC10_VECTOR, NMI_VECTOR, PORT1_VECTOR, WDT_VECTOR, USCIAB0TX_VECTOR, \
		USCIAB0RX_VECTOR, COMPARATORA_VECTOR, TIMER1_A1_VECTOR, TIMER1_A0_VECTOR

__interrupt void TrapIsr(void)
{
  // You can trap the CPU here but it is not crucial in this project
}

Here you can see screenshots from schematics and PCB layout from Cadence:

Schematics

Schematics

PCB Layout

PCB Layout

Programming the Board via Spy-Bi-Wire Interface

Programming the Board via Spy-Bi-Wire Interface

I used Spy-Bi-Wire interface to program the board. MSP430 Launchpad has its own emulator which can be used to program external chips. Connection is explained in the readme file.Some Important Points

1. Spy-Bi-Wire Connection: You must use short cables and all four cables must be in the same length because SBW connection is very fast and therefore too noise-sensitive. Also you have to be careful when connecting the cable. If you put it in wrong way, you will apply -3.3V, which will damage your chip permanently.

2. Reset Pin Connection: You must Pull-up the reset pin with a resistor 47k Ohms and connect it to ground via 2.2nF capacitor. This value is critical that if you exceed it SBW connection won’t work. You can use smaller values, however.

3. Soldering Reed Switch: Reed switch is higly fraigle and can be broken even you hold it, or bend its connecting legs. Best way to bend it is using two twizzers. Hold isd close to glass part with one and use the other to bend. A clicking noise occurs when you keep a magnet close, you can check wheter it is working or not.

4. Decoupling capacitor: This capacitor is connected to protect the chip from glitches in the power supply and also used to provide charge if power is reduced in short term. You need to keep that capacitor close to the Vcc pin of the MCU or it does not serve well as a charge storage.

5. Placing the Board: You should tightly connect the board to the rims. The closer it to the tire the better the display. And you should place the magnet right across the reed switch. Careful about the distance between the magnet and the switch. It must not hit the switch and it should not move because if board attracts and removes the magnet it can break the switch.

Completed Board

Completed Board

Implementation on a Stripboard

Implementation on a Stripboard

Connection of The Board

Connection of The Board

RESULTS

Speed  (km/h) and Total Distance Covered (m)

Speed (km/h) and Total Distance Covered (m)

Constant Display

Constant Display

Project Folder

You can download the project folder from this link: https://drive.google.com/folderview?id=0BzZ428em_Z09QmFubm41NTNicjQ&usp=sharing

Acknowledgements

I would like to thank a lot to everyone in Saykal Electronics, Yücel Saykal, Yasin Taşan, Anıl Sırma, and Kerim Atak for their huge help on every stages of the project from idea to production. I appreciate their advices and physical contribution. I also would like to thank to TI E2E Community Forum, which kindly helped whenever I needed.

Please feel free to contact me for any comments, correction, objections etc. This is an open source project and I claim no right and accept no responsibilty for user’s application.

Günay Turan

 

Kitchen Alarm with Assembly

Hi,
On this entry, I will present a simple group project we did during my microprocessors course with Atmel ATmega 16. This is my first assembly project, even though the idea is simple (This project can be developed in one or two hours with C) application on assembly is a bit complicated due to the fact that the language is really primitive compared to C.
So why we still use assembly? First, it gives a better understanding of the mechanisms working in the processor because you are closer to the MCU somehow, and second, it has much smaller code size compared to C. So if your flash is limited, and code is complicated it makes easier to fit the code in the flash memory in the expense of harder implementation of the idea.
Project Description:
Kitchen alarm basicly is a countdown timer with which you set a time, start the coundown and get an alarm sound when the time is up. In this application we have a limit of 9:59 minutes because we only used three 7-segment displays. And as an additional feature we have an time addition button which adds one extra minute anytime the button is pressed without interrupting the countdown.
How to Use:
There are three buttons to control the Kitchen Alarm.
Increment Button: Each time pressed, this button increases the number displayed on the active 7-segment display before the timer started to work. When the countdown is started the button gets deactivated. If the number displayed reaches to nine and the button is pressed again, display rolls back to zero again.
Set Button: This button is used to save the digit calibrated by the increment button and activates the more significant digit. When the program is run, the least significant (rightmost) digit is active initially. After choosing the value via increment button, pressing this button saves that value and activates the next one. Doing it second time sets the second digit and activates the minute segment and the third press of the button sets the value and starts the timer. After the third press countdown starts immidately, and there is no turning back after this point. User should reset the program to make a change in the top value.
Add Minute Button: As explained above, this button is always active during and the before coundown and increases the minute by one. If the value is nine at the minute segment it changes nothing.
Algorithm:
The flowchart of the program, excluding the display function, is the following:
Flowchart of the Program
The display function is excluded because it is done by using brute force using lots of comparisons of the same type, which will be more clear when the code is read.
Assembly Code:
;VUT v Brne FEKT
;CMPT Course 1st Project: Kitchen Alarm
;
;Authors:
;	Burak Dalogullari
;	Gunay Turan
;	Kerem Sennik
;	Serkan Ozatik
;	Alican Bor
;
;How to use:
;	Onboard Switches:	|o|		|o|		|o|
;				SW2		SW1		SW0
;				Time addition	Set	   Increment
;
;	7-Segment Displays:	A   	: 	 B		C
;				minute		   seconds
;
;	When the program is excecuted, the user will see 0:00 on
;the display. At that time the active display is display-"C".
;user can use increment button(SW0) to determine that value.
;pressing the set button(SW1) will set C value and activate
;the display-B. With the same procedure user can set A value
;and setting this will automatically start the countdown.
;During the countdown user can use SW2 to get an additional
;minute. When display shows 0:00 again, buzzer will give an
;alarm signal until a reset or powerdown is applied.
;

.include "m16def_mod.inc"		;include file

;register definitions
.def    delay_idx = r16
.def    temp = r17
.def    idxA = r18
.def    idxB = r19
.def    idxC = r20
.def    zero = r21
.def    one = r22
.def    two = r23
.def    three = r24
.def    four = r25
.def    five = r26
.def    six = r27
.def    seven = r28
.def    eight = r29
.def    nine = r30
.def    set_idx = r31

; interrupt vectors
.cseg
.org	    0x0000
	rjmp	reset
.org	    0x0002
    rjmp    increment_sw_debouncer
.org        0x0004
    rjmp    set_sw_debouncer
.org        0x0012
    rjmp    time
.org        0x0024
    rjmp    addminute_sw_debouncer

;main program
.org	    0x002a
reset:
	;Stack definition
	ldi		temp, LOW(RAMEND)
	out     	SPL, temp
	ldi		temp, HIGH(RAMEND)
	out		SPH, temp

	;Digital port directions
	ldi		temp, 255
	out		DDRA, temp
	ldi     	temp, 251
	out		DDRB, temp
	ldi  	   	temp, 255
	out     	DDRC, temp
	ldi     	temp, 243
	out		DDRD, temp

	;external input settings
	;interrupts 0,1, and 2 at falling edge
	ldi		temp, (1<<INT0) | (1<<INT1) | (1<<INT2)
	out		GICR, temp
	ldi		temp, (1<<ISC01) | (0<<ISC00) | (1<<ISC11) | (0<<ISC10)
	out		MCUCR, temp

	;Digital IO configurations for 7-segment display for each digit
	ldi     zero, 0x7B
	ldi     one, 0x0A
	ldi     two, 0xB3
	ldi     three, 0x9B
	ldi     four, 0xCA
	ldi     five, 0xD9
	ldi     six, 0xF9
	ldi     seven, 0x0B
	ldi     eight, 0xFB
	ldi     nine, 0xDB

	;index initializations
	ldi     set_idx, 0
	ldi     idxA, 0
	ldi     idxB, 0
	ldi     idxC, 0
	ldi     delay_idx, 3

	;output initialization
	out     PORTA, zero
	out     PORTB, zero
	out     PORTC, zero

	sei					;enable all interrupts
;infinite loop
loop:
    	sei	            		;reenable interrupts
	rjmp    displayA			;display function call
	rjmp	loop

;software debouncer function for increment button
increment_sw_debouncer:
    ldi     temp,255
delay_loop_1:
    dec     temp
    brne    delay_loop_1
    rjmp    increment

;increment button interrupt service routine
increment:
    ldi     temp, 0
    CPSE    set_idx, temp
    rjmp    check1
    rjmp    setC

check1:
    ldi     temp, 1
    CPSE    set_idx, temp
    rjmp    setA
    rjmp    setB

setC:
    inc     idxC
    ldi     temp, 10
    CPSE    idxC, temp
    rjmp    loop
    ldi     idxC, 0
    rjmp    loop

setB:
    inc     idxB
    ldi     temp, 6
    CPSE    idxB, temp
    rjmp    loop
    ldi     idxB, 0
    rjmp    loop

setA:
    inc     idxA
    ldi     temp, 10
    CPSE    idxA, temp
    rjmp    loop
    ldi     idxA, 0
    rjmp    loop

;timer enable after the time is set
timer_enable:

	;normal mode with prescaler 1024 - TCLK ~= 1kHz
	ldi 	temp, (1<<CS02) | (0<<CS01) | (1<<CS00)
	out 	TCCR0, temp
	ldi 	temp, (1<<TOIE0)
	out 	TIMSK, temp
	rjmp    loop

;Software debouncer for set button
set_sw_debouncer:
    ldi     temp,255
delay_loop_2:
    dec     temp
    brne    delay_loop_2
    rjmp    set

;Set button interrupt service routine
set:
    inc     set_idx
    ldi     temp, 3
    CPSE    set_idx, temp
    reti
    rjmp    timer_enable
    reti

;SW debouncer for time addition button
addminute_sw_debouncer:
    ldi     temp,255
delay_loop_3:
    dec     temp
    brne    delay_loop_3
    rjmp    add_minute

;Time addition button ISR
add_minute:
    inc     idxA
    ldi     temp, 10
    CPSE    idxA, temp
    reti
    ldi     idxA, 9
    reti

;Timer overflow ISR
time:
    dec     delay_idx
    ldi     temp, 0
    CPSE    delay_idx, temp
    reti
    ldi     delay_idx, 3
    rjmp    decC
reti

decC:
    dec     idxC
    ldi     temp, 255
    CPSE    idxC, temp
    rjmp    loop
    rjmp    decB

decB:
    ldi     idxC, 9
    dec     idxB
    ldi     temp, 255
    CPSE    idxB, temp
    rjmp    loop
    rjmp    decA

decA:
    ldi     idxB, 5
    dec     idxA
    ldi     temp, 255
    CPSE    idxA, temp
    rjmp    loop
    rjmp    alarm

;Display functions for each digit and each 7-segment display
displayA:
    ldi     temp, 0
    CPSE    temp, idxA
    rjmp    displayA1
    out     PORTA, zero
    rjmp    displayB

displayA1:
    ldi     temp, 1
    CPSE    temp, idxA
    rjmp    displayA2
    out     PORTA, one
    rjmp    displayB

displayA2:
    ldi     temp, 2
    CPSE    temp, idxA
    rjmp    displayA3
    out     PORTA, two
    rjmp    displayB

displayA3:
    ldi     temp, 3
    CPSE    temp, idxA
    rjmp    displayA4
    out     PORTA, three
    rjmp    displayB

displayA4:
    ldi     temp, 4
    CPSE    temp, idxA
    rjmp    displayA5
    out     PORTA, four
    rjmp    displayB

displayA5:
    ldi     temp, 5
    CPSE    temp, idxA
    rjmp    displayA6
    out     PORTA, five
    rjmp    displayB

displayA6:
    ldi     temp, 6
    CPSE    temp, idxA
    rjmp    displayA7
    out     PORTA, six
    rjmp    displayB

displayA7:
    ldi     temp, 7
    CPSE    temp, idxA
    rjmp    displayA8
    out     PORTA, seven
    rjmp    displayB

displayA8:
    ldi     temp, 8
    CPSE    temp, idxA
    rjmp    displayA9
    out     PORTA, eight
    rjmp    displayB

displayA9:
    out     PORTA, nine
    rjmp    displayB

displayB:
    ldi     temp, 0
    CPSE    temp, idxB
    rjmp    displayB1
    out     PORTB, zero
    rjmp    displayC

displayB1:
    ldi     temp, 1
    CPSE    temp, idxB
    rjmp    displayB2
    out     PORTB, one
    rjmp    displayC

displayB2:
    ldi     temp, 2
    CPSE    temp, idxB
    rjmp    displayB3
    out     PORTB, two
    rjmp    displayC

displayB3:
    ldi     temp, 3
    CPSE    temp, idxB
    rjmp    displayB4
    out     PORTB, three
    rjmp    displayC

displayB4:
    ldi     temp, 4
    CPSE    temp, idxB
    rjmp    displayB5
    out     PORTB, four
    rjmp    displayC

displayB5:
    out     PORTB, five
    rjmp    displayC

displayC:
    ldi     temp, 0
    CPSE    temp, idxC
    rjmp    displayC1
    out     PORTC, zero
    rjmp    loop

displayC1:
    ldi     temp, 1
    CPSE    temp, idxC
    rjmp    displayC2
    out     PORTC, one
    rjmp    loop

displayC2:
    ldi     temp, 2
    CPSE    temp, idxC
    rjmp    displayC3
    out     PORTC, two
    rjmp    loop

displayC3:
    ldi     temp, 3
    CPSE    temp, idxC
    rjmp    displayC4
    out     PORTC, three
    rjmp    loop

displayC4:
    ldi     temp, 4
    CPSE    temp, idxC
    rjmp    displayC5
    out     PORTC, four
    rjmp    loop

displayC5:
    ldi     temp, 5
    CPSE    temp, idxC
    rjmp    displayC6
    out     PORTC, five
    rjmp    loop

displayC6:
    ldi     temp, 6
    CPSE    temp, idxC
    rjmp    displayC7
    out     PORTC, six
    rjmp    loop

displayC7:
    ldi     temp, 7
    CPSE    temp, idxC
    rjmp    displayC8
    out     PORTC, seven
    rjmp    loop

displayC8:
    ldi     temp, 8
    CPSE    temp, idxC
    rjmp    displayC9
    out     PORTC, eight
    rjmp    loop

displayC9:
    out     PORTC, nine
    rjmp    loop

;Alarm function with infinite loop
alarm:
    ldi     temp, 127
    out     PORTA, temp
    rjmp    finish

finish:
    rjmp    alarm

Simulation:

To simulate the project before implementing it on hardware we used Proteus ISIS with which you can program MCU’s with provided hex files and observe the results. You can reach the simulation file from this link.

Schematics

Hardware:

The hardware part is simple. We used three seven-segments, each segment directly connected to a digital I/O port (no encoding/decoding). For interrupts we used the onboard switches on the development kit, and lastly for the alarm sound we used a DC buzzer of 5V, which is controlled by one digital I/O port.

Team:

Kerem Şennik, Burak Daloğulları, Me, Serkan Özatik

Please feel free to contact anything about the project. This project is open source that you can use freely but the authors have no responsibility for its misusage.

Günay Turan