Posted on Leave a comment

AVRHexFlashGUI: an AVRdude Hex Flasher GUI Application with Quick Flash Floating Button

Introduction

To program the AVR microcontroller you use “avrdude” to flash the hex files in the microcontroller. avrdude is a command line tool. Every time you have to flash the microcontroller with a new file you have to write the command.
This AVRHexFlashGUI uses the avrdude software to flash the microcontroller but it provides a Graphical User Interface.

There is a special feature I have implemented which is “QuickSet” Toolbar which has a “Quick FLASH” button. This toolbar floats on top of the screen. And provides an easy-to-use button for flashing the firmware files. It has an indicator that turns it’s color to green if the programming is successful or turns red if the programming is unsuccessful. It also has an ‘X’ button to switch off this toolbar.
Software such as microchip mplab x ide does not have an option to integrate the avrdude commands like in microchip studio(formerly Atmel Studio). This toolbar provides an easy access to flashing. You do not have to do anything else. Just launch the program and enable the QuickSet toolbar.

AVR microcontrollers are widely used in embedded systems development due to their versatility and ease of programming and also their low cost. Flashing firmware or code onto these microcontrollers is a fundamental task during development. The AVRHexFlashGUI is a graphical user interface (GUI) application built using the Tkinter library in Python.

Note: avrdude software must be installed on your system.
Download avrdude for windows https://github.com/avrdudes/avrdude/releases

Download

Screenshots

Overview

The AVRHexFlashGUI application streamlines the process of flashing firmware onto AVR microcontrollers using a user-friendly interface. It offers the following features:

  1. Microcontroller Selection: The application allows users to select the target microcontroller from a list of supported devices.
  2. Programmer Configuration: Users can specify the programmer to be used for flashing the firmware. This information is crucial for establishing a connection between the computer and the microcontroller.
  3. HEX File Selection: Users can browse their computer for the HEX file containing the firmware they want to flash onto the microcontroller.
  4. Quick Flash: The application provides a quick flash button that initiates the flashing process using the selected programmer, microcontroller, and HEX file.
  5. Read the Microcontroller Flash Memory: The user can read the unprotected flash memory and save it in the program folder as “HexFromDevice.hex”
  6. AVR Fuse Settings: The application offers the ability to read and display the current fuse settings of the connected AVR microcontroller. Users can also write new fuse settings if needed.
  7. Output Display: The application displays the output of the avrdude tool, which is responsible for flashing the firmware and handling the communication with the microcontroller.

Usage

To use the AVRHexFlashGUI application, follow these steps:

  1. Launch the application.
  2. Select the target microcontroller.
  3. Specify the programmer to be used for flashing.
  4. Load the desired HEX file by clicking the “Browse” button.
  5. Click the “Program AVR” button to start the flashing process.
  6. Monitor the avrdude output to ensure successful flashing.
  7. Optionally, use the “Fuse Setting” section to read, modify, and write fuse settings.
AVRHexFlash Quick Flash Toolbar with MPLAB X IDE

Conclusion

The AVRHexFlashGUI application simplifies the process of flashing firmware onto AVR microcontrollers by providing an intuitive and user-friendly interface. With features for microcontroller selection, programmer configuration, HEX file loading, and fuse settings, developers can efficiently program their microcontrollers. The use of Tkinter and Python makes it easy to create and customize the GUI, enhancing the overall user experience. This application is a valuable tool for both beginners and experienced developers working with AVR microcontrollers. By streamlining the flashing process, it helps save time and ensures accurate firmware deployment.

Read more: AVRHexFlashGUI: an AVRdude Hex Flasher GUI Application with Quick Flash Floating Button

Code

from tkinter import *
from tkinter import ttk
import serial
import serial.tools.list_ports
import datetime
import os
import sys
import subprocess
from tkinter import filedialog
# Get the directory of the script
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))

# Change the working directory to the script's directory
os.chdir(script_dir)
# For debug messages to be printed
dbg_msg = 0

root = Tk()
root.title("AVRHexFlashGUI")
root.resizable(False, False)  # Prevent window from being resized
sty = ttk.Style()
sty.theme_use("default")
#print(sty.theme_names())
root.columnconfigure(0, weight = 1)
root.rowconfigure(0, weight = 1)
# Frame Define START
Frame00 = ttk.LabelFrame(root, text = "AVRHexFlashGUI", padding = 10)
Frame00.grid(column = 0, row = 0, sticky =  (W,E) )
Frame01 = ttk.LabelFrame(root, text = "Fuse Setting", padding = 10)
Frame01.grid(column = 2, row = 0, sticky =  (W,E) )
## Frame Define START
canvas = None
circle = None
# Frame00 START
label0011 = ttk.Label(Frame00, text = "Microcontroller")
label0011.grid(column = 0, row = 0, sticky = (N,E,W,S))

MCU_save_stored_value = StringVar()
MCU_save_stored_value_display = Text(Frame00, height = 1, width = 2, wrap=WORD)
MCU_save_stored_value_display.grid(row = 0, column = 1, sticky = (N,E,W,S))
# Display the file value
file_path = os.path.join("Data", "MCU_save.txt")
if os.path.exists(file_path):
    with open(file_path, 'r') as file:
        MCU_value = file.read()
        MCU_save_stored_value_display.insert(END, MCU_value)

programmer_label = ttk.Label(Frame00, text="Programmer:")
programmer_label.grid(row = 0, column  = 2, sticky = (N,E,W,S))
programmer_var = StringVar()
programmer_var_display = Text(Frame00, height = 1, width = 10, wrap=WORD)
programmer_var_display.grid(row = 0, column = 3, sticky = (N,E,W,S))
# Display the file value
file_path = os.path.join("Data", "avrdude_programmer.txt")
if os.path.exists(file_path):
    with open(file_path, 'r') as file:
        Prog_value = file.read()
        programmer_var_display.insert(END, Prog_value)


label0010 = ttk.Label(Frame00, text="Select HEX File:")
label0010.grid(row = 1, column  = 0, sticky = (N,E,W,S))
hex_file_path = StringVar()
hex_file_path.set("Load your File")

def browse_file():
    file_path = filedialog.askopenfilename(filetypes=[("HEX Files", "*.hex")])
    if file_path:
        hex_file_path.delete(1.0, END)  # Clear previous text
        hex_file_path.insert(END, file_path)
        hex_file_path.config(wrap=WORD)  # Enable text wrapping

    new_complete_command = ">> avrdude " + "-c " + programmer_var_display.get('1.0', 'end-1c') \
                           + " -p " + MCU_save_stored_value_display.get('1.0', 'end-1c') \
                           + " -U " + "flash:w:" + hex_file_path.get('1.0', 'end-1c') + ":i"
    complete_command_var.set(new_complete_command)

def copy_to_clipboard():
    start_index = hex_file_path.index(SEL_FIRST)
    end_index = hex_file_path.index(SEL_LAST)
    
    if start_index and end_index:
        selected_text = hex_file_path.get(start_index, end_index)
        root.clipboard_clear()
        root.clipboard_append(selected_text)
        root.update()
def save_to_file():
    file_path = os.path.join("Data", "Hex_save_location.txt")
    with open(file_path, 'w') as file:
        file.write(hex_file_path.get('1.0', 'end-1c'))
    file_path = os.path.join("Data", "avrdude_programmer.txt")
    with open(file_path, 'w') as file:
        file.write(programmer_var_display.get('1.0', 'end-1c'))
    file_path = os.path.join("Data", "MCU_save.txt")
    with open(file_path, 'w') as file:
        file.write(MCU_save_stored_value_display.get('1.0', 'end-1c'))

def getMcuList():    
    prog_name = programmer_var_display.get('1.0','end-1c')
    command = ['avrdude','-c',prog_name]
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    
    output_lines = result.stderr.split('\n')
    for line in output_lines:
        display_avrdude_output.insert(END, line + '\n')
    display_avrdude_output.see("end")

def DumpHex():    
    prog_name = programmer_var_display.get('1.0','end-1c')
    part_name = MCU_save_stored_value_display.get('1.0','end-1c')
    command = ['avrdude','-c',prog_name,'-p',part_name,'-U', 'flash:r:HexFromDevice.hex:i']
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    display_avrdude_output.delete('1.0',END)
    output_lines = result.stderr.split('\n')
    for line in output_lines:
        display_avrdude_output.insert(END, line + '\n')
    display_avrdude_output.see("end")    
    
button0011 = ttk.Button(Frame00, text="Browse", command=browse_file)
button0011.grid(row = 1, column  = 1,sticky = (N,E,W,S))

McuListbutton = ttk.Button(Frame00, text="Get Supported\nMCU List", command=getMcuList)
McuListbutton.grid(row = 1, column  = 3,sticky = (N,E,W,S))
DumpHexbutton = ttk.Button(Frame00, text="Read Device\nSave File", command=DumpHex)
DumpHexbutton.grid(row = 1, column  = 2,sticky = (N,E,W,S))

hex_file_path = Text(Frame00, height=5, width=50, wrap=WORD)
hex_file_path.grid(row=2, column=0, columnspan=2, sticky=(N,E,W,S))
# Display the file value
file_path = os.path.join("Data", "Hex_save_location.txt")
if os.path.exists(file_path):
    with open(file_path, 'r') as file:
        Hex_file_value = file.read()
        hex_file_path.insert(END, Hex_file_value)
# Bind right-click context menu to hex_file_path
context_menu = Menu(hex_file_path, tearoff=0)
context_menu.add_command(label="Copy", command=copy_to_clipboard)
hex_file_path.bind("<Button-3>", lambda event: context_menu.post(event.x_root, event.y_root))
button_save = ttk.Button(Frame00, text="Save to File", command=save_to_file)
button_save.grid(row=5, column=0, sticky=(N,E,W,S))


complete_command_var = StringVar()
Complete_Command = ">> avrdude "+"-c "+programmer_var_display.get('1.0','end-1c') \
                   +" -p "+MCU_save_stored_value_display.get('1.0','end-1c')\
                   +" -U "+"flash:w:"+hex_file_path.get('1.0','end-1c')+":i"
complete_command_var.set(Complete_Command)
display_complete_command = ttk.Label(Frame00, wraplength=500, textvariable= complete_command_var)
display_complete_command.grid(row=4, column=0, sticky=(N,E,W,S))


def program_avr():
    display_avrdude_output.delete('1.0',END)
    prog_name = programmer_var_display.get('1.0','end-1c')
    part_name = MCU_save_stored_value_display.get('1.0','end-1c')
    hex_file_address = hex_file_path.get('1.0','end-1c')
    flash_statement = "flash:w:"+hex_file_address+":i"
    
    command = ['avrdude','-c',prog_name,'-p',part_name,'-U',flash_statement]
    print(command)
    
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    
    # Analyze the result and indicate success or failure
    output_lines = result.stderr.split('\n')
    success_indication = "flash verified\n\navrdude done.  Thank you."
    success_indication_1 = "flash verified\n\navrdude: safemode: Fuses OK\n\navrdude done.  Thank you."
    success = success_indication in result.stderr or success_indication_1 in result.stderr

    for line in output_lines:
        display_avrdude_output.insert(END, line + '\n')

    display_avrdude_output.see("end")

    if success:
        print("Programming successful!")
        try:
            canvas.itemconfig(circle, fill='#aaff00')
        except:
            pass
        
    else:
        print("Programming failed.")
        try:
            canvas.itemconfig(circle, fill='red')
        except:
            pass
        

sty.configure("Color.TButton", background="blue", foreground="white")
button0021 = ttk.Button(Frame00,style='Color.TButton', text="Program AVR", command=program_avr)
button0021.grid(row = 5, column  = 1)

display_avrdude_output = Text(Frame00, height=10, width=50, wrap=WORD)
display_avrdude_output.grid(row=6, columnspan=4, sticky=(N,E,W,S))
scrollbar = Scrollbar(Frame00, command=display_avrdude_output.yview)
scrollbar.grid(row=6, column=4, sticky=(N, S))
display_avrdude_output.config(yscrollcommand=scrollbar.set)

Abhay_text = "This program is writtent by : ABHAY KANT\nvisit: https://exasub.com"
AbhayLabel = ttk.Label(Frame00, text=Abhay_text)
AbhayLabel.grid(row = 11, column  = 0, sticky = (N,E,W,S))
## Frame00 END

def donothing():
   filewin = Toplevel(root)
   button = Button(filewin, text="Do nothing button")
   button.pack()

about_window = None  
def About_me():
    global about_window
    
    if about_window is None or not about_window.winfo_exists():
        about_window = Toplevel(root)
        about_window.title("About Me")
        
        label1 = ttk.Label(about_window, text="EXASUB.COM")
        label1.pack()
        
        button = ttk.Button(about_window, text="Quit", command=about_window.destroy)
        button.pack()
    else:
        about_window.lift()  

hfuse_read_value_display = None
lfuse_read_value_display = None
def Read_fuse():
    hfuse_read_value_display.delete('1.0',END)
    lfuse_read_value_display.delete('1.0',END)
    prog_name = programmer_var_display.get('1.0','end-1c')
    part_name = MCU_save_stored_value_display.get('1.0','end-1c')
    
    hfuse_statement = "hfuse:r:-:h"
    lfuse_statement = "lfuse:r:-:h"
    
    command = ['avrdude','-c',prog_name,'-p',part_name,'-U',hfuse_statement]
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    hfuse_read_value_display.insert(END,result.stdout)
    
    command = ['avrdude','-c',prog_name,'-p',part_name,'-U',lfuse_statement]
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    lfuse_read_value_display.insert(END,result.stdout)

    
fusesetwin = None
fusesetwin_write = None
def FuseSet():
    global fusesetwin,hfuse_read_value_display,lfuse_read_value_display
    if fusesetwin is None or not fusesetwin.winfo_exists():
            
        fusesetwin = Toplevel(root)
        
        button = Button(fusesetwin, text="Quit", command = fusesetwin.destroy)
        button.grid(column = 0, row = 0, sticky = (N,E,W,S))
        Read_Fuse_label = ttk.Label(fusesetwin, text="Read Fuse Values")
        Read_Fuse_label.grid( row = 1,column = 0, sticky = (N,E,W,S))

        read_fuse_button = Button(fusesetwin, text="Read", command = Read_fuse)
        read_fuse_button.grid(column = 1, row = 1, sticky = (N,E,W,S))

        hFuse_label = ttk.Label(fusesetwin, text="hFuse ")
        hFuse_label.grid( row = 2,column = 0, sticky = (N,E,W,S))
        hfuse_read_value_display = Text(fusesetwin, height = 1, width = 6, wrap=WORD)
        hfuse_read_value_display.grid(row = 2, column = 1, sticky = (N,E,W,S))

        lFuse_label = ttk.Label(fusesetwin, text="lFuse ")
        lFuse_label.grid( row = 3,column = 0, sticky = (N,E,W,S))
        lfuse_read_value_display = Text(fusesetwin, height = 1, width = 6, wrap=WORD)
        lfuse_read_value_display.grid(row = 3, column = 1, sticky = (N,E,W,S))
        # Separator object
        separator = ttk.Separator(fusesetwin, orient='horizontal')
        separator.grid(column = 0,columnspan=2, row = 4, sticky = (N,E,W,S))
            
        
        
        
        def fuseWrite():
            global fusesetwin_write
            if fusesetwin_write is None or not fusesetwin_write.winfo_exists():
                fusesetwin_write = Toplevel(root)
                label0101 = ttk.Label(fusesetwin_write, text = "Stored Default Fuse Settings")
                label0101.grid(column = 0, row = 0, sticky = (N,E,W,S))

                label0110 = ttk.Label(fusesetwin_write, text = "hFuse")
                label0110.grid( row = 1,column = 0, sticky = (N,E,W,S))
                hfuse_stored_value = StringVar()
                hfuse_stored_value_display = Text(fusesetwin_write, height = 1, width = 6, wrap=WORD)
                hfuse_stored_value_display.grid(row = 1, column = 1, sticky = (N,E,W,S))
                # Display the file value
                file_path = os.path.join("Data", "Fuse_hfuse.txt")
                if os.path.exists(file_path):
                    with open(file_path, 'r') as file:
                        fuse_value = file.read()
                        hfuse_stored_value_display.insert(END, fuse_value)


                label0120 = ttk.Label(fusesetwin_write, text = "lFuse")
                label0120.grid( row = 2,column = 0, sticky = (N,E,W,S))
                lfuse_stored_value = StringVar()
                lfuse_stored_value_display = Text(fusesetwin_write, height = 1, width = 6, wrap=WORD)
                lfuse_stored_value_display.grid(row = 2, column = 1, sticky = (N,E,W,S))
                # Display the file value
                file_path = os.path.join("Data", "Fuse_lfuse.txt")
                if os.path.exists(file_path):
                    with open(file_path, 'r') as file:
                        fuse_value = file.read()
                        lfuse_stored_value_display.insert(END, fuse_value)
                def flashfuse():
                    
                    prog_name = programmer_var_display.get('1.0','end-1c')
                    part_name = MCU_save_stored_value_display.get('1.0','end-1c')
                    
                    hfuse_statement = "hfuse:w:"+hfuse_stored_value_display.get('1.0', 'end-1c')+":m"
                    lfuse_statement = "lfuse:w:"+lfuse_stored_value_display.get('1.0', 'end-1c')+":m"
                    print(hfuse_statement)
                    print(lfuse_statement)
                    command = ['avrdude','-c',prog_name,'-p',part_name,'-U',hfuse_statement]
                    result = subprocess.run(command, shell=True, capture_output=True, text=True)
                    
                    output_lines = result.stderr.split('\n')
                    success_indication = "flash verified\n\navrdude done.  Thank you."
                    success = success_indication in result.stderr

                    for line in output_lines:
                        display_avrdude_output.insert(END, line + '\n')

                    display_avrdude_output.see("end")

                    if success:
                        print("Programming successful!")
                        try:
                            canvas.itemconfig(circle, fill='#aaff00')
                        except:
                            pass
                        
                    else:
                        print("Programming failed.")
                        try:
                            canvas.itemconfig(circle, fill='red')
                        except:
                            pass
                        
                    
                    command = ['avrdude','-c',prog_name,'-p',part_name,'-U',lfuse_statement]
                    result = subprocess.run(command, shell=True, capture_output=True, text=True)
                    
                    output_lines = result.stderr.split('\n')
                    success_indication = "flash verified\n\navrdude done.  Thank you."
                    success = success_indication in result.stderr

                    for line in output_lines:
                        display_avrdude_output.insert(END, line + '\n')

                    display_avrdude_output.see("end")

                    if success:
                        print("Programming successful!")
                        try:
                            canvas.itemconfig(circle, fill='#aaff00')
                        except:
                            pass
                        
                    else:
                        print("Programming failed.")
                        try:
                            canvas.itemconfig(circle, fill='red')
                        except:
                            pass
                        
                
                WriteFusebutton = Button(fusesetwin_write,text="Write Fuse", command = flashfuse)
                WriteFusebutton.grid(row = 0, column=1, sticky= (N,W,E,S))
                NoteLabelText = "Note: Change the  fuse setting carefully. \
                                \nPlease check the datasheet for correct fuse setting \
                                \nWrong Fuse setting may disable programming using programmer\
                                \nIf programmer is disabled you will need the offical ATMEL programmer"
                NoteLabel = ttk.Label(fusesetwin_write, wraplength = 500, text = NoteLabelText)
                NoteLabel.grid(column = 0, columnspan = 2, row = 10, sticky = (N,E,W,S))
            else:
                fusesetwin_write.lift()
        Write_Fuse_button = Button(fusesetwin, text="Write Fuse", command = fuseWrite)
        Write_Fuse_button.grid(column = 0,row = 5, sticky = (N,E,W,S))
    else:
         fusesetwin.lift()

quicksetwin = None
startx = 0
starty = 0

def move_window(event):
    global startx, starty
    x = quicksetwin.winfo_pointerx() - startx
    y = quicksetwin.winfo_pointery() - starty
    quicksetwin.geometry(f"+{x}+{y}")
    startx = quicksetwin.winfo_pointerx() - x
    starty = quicksetwin.winfo_pointery() - y


def Quick_set():
    global quicksetwin, startx, starty, circle,canvas
    if quicksetwin is None or not quicksetwin.winfo_exists():
        quicksetwin = Toplevel(root)
        quicksetwin.attributes("-topmost", True)  # Set the window to stay on top
        quicksetwin.overrideredirect(True)  # Remove window decorations
        
        title_bar = Frame(quicksetwin, bg="gray", relief="raised", bd=2)
        title_bar.grid(column=0, row=0, columnspan=3, sticky=(N, E, W))
        title_bar.bind("<ButtonPress-1>", start_move)
        title_bar.bind("<B1-Motion>", move_window)
        
        quickbutton = Button(title_bar, text="X", command=quicksetwin.destroy)
        quickbutton.grid(column=3, row=0, sticky=(N, E, W))
        
        quickProgbutton = Button(title_bar, text="Quick FLASH", command=program_avr)
        quickProgbutton.grid(column=1, row=0, sticky=(N, E, W), padx=5)
        global circle,canvas
        canvas = Canvas(title_bar, width=20, height=20)
        canvas.grid(column=2, row=0, sticky=(N, E, W), padx=5)

        # Draw a circle initially with a default color
        circle = canvas.create_oval(2, 2, 19, 19, fill='gray')
        
    else:
        quicksetwin.lift()

def start_move(event):
    global startx, starty
    startx = event.x
    starty = event.y



menubar = Menu(root)

Fusemenu = Menu(menubar, tearoff=1)
menubar.add_cascade(label="Fuse Setting", command=FuseSet)


menubar.add_command(label = "EXASUB.com", command = About_me)

menubar.add_command(label = "Quit", command = root.destroy)
menubar.add_command(label = "QuickSet", command = Quick_set)
root.config(menu=menubar)
if __name__ == "__main__":
    root.mainloop()
Posted on Leave a comment

How to use UART and Interrupts of PIC16F877A

The PIC16F877A has a single ISR function which is called every time some things generate an interrupt. Inside the ISR you have to check for individual flag bits to know which peripheral has generated that interrupt.

/* 
 * File:   main.c
 * Author: abhay
 *
 * Created on July 15, 2023, 11:48 PM
 */

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#define _XTAL_FREQ 16000000
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include<string.h>




void GPIO_init(void);
void UART_init(void) ;
void tx(unsigned char a);
unsigned char rx();
void txstr(unsigned char *s);

uint8_t RX_chr;
void clear_flag(void);
unsigned char msgACK[16] = "Received";

void __interrupt() myISR() {
    if (RCIF == 1) {
        if (RCSTAbits.OERR) {
            CREN = 0;
            NOP();
            CREN = 1;
        }
        RX_chr = RCREG;
 
        RCIF = 0;
    }

}

/*
 * 
 */
int main(int argc, char** argv) {
    GPIO_init();
    UART_init();
    

    /*
     * Interrupt Setup START
     */
    INTCONbits.GIE = 1;
    INTCONbits.PEIE = 1;
    PIE1bits.TXIE = 0;
    PIE1bits.RCIE = 1;
    /*
     * Interrupt Setup END
     */


    while (1) {

        if (RX_chr == '0') {
        tx(RX_chr);
        RD1 = 0 ; // LED OFF
        txstr(msgACK);
        clear_flag();
    }

    if (RX_chr == 'a') {
        RD1 = 1; // LED ON
        txstr(msgACK);
        clear_flag();
    }

    }

    return (EXIT_SUCCESS);
}


void clear_flag(void) {
    RX_chr = 0;
}

void GPIO_init(void) {

    TRISD &= ~(1 << 1);
    RD1 = 0;
    
    /*
     * UART Pins Initialize
     */
    TRISCbits.TRISC6 = 0; // TX
    TRISCbits.TRISC7 = 1; //RX


}
void UART_init() {
        TXSTAbits.CSRC = 0;
        TXSTAbits.TX9 = 0;
        TXSTAbits.TXEN = 1;
        TXSTAbits.SYNC = 0;
        /* BRGH - High Baud Rate Select Bit
         * 1: High Speed
         * 0: Low Speed
         */
        TXSTAbits.BRGH = 1;
        TXSTAbits.TRMT = 0;
        TXSTAbits.TX9D = 0;

        RCSTAbits.SPEN = 1;
        RCSTAbits.RX9 = 0;
        RCSTAbits.SREN = 0;
        RCSTAbits.CREN = 1;
        RCSTAbits.ADDEN = 0;
        RCSTAbits.FERR = 0;
        RCSTAbits.OERR = 0;
        RCSTAbits.RX9D = 0;

        /*
         * Baud Rate Formula
         *  Asynchronous 
         *      Baud Rate = Fosc / (64 (x + 1))
         *          Baud Rate x (64 (x+1)) = FOSC
         *        SPBRG=  (x) = ( Fosc/( Baud_Rate * 64 ) ) - 1
         */

        SPBRG = 103; // Baud Rate 9600
        TXIF = RCIF = 0;
    }

void tx(unsigned char a) {
        
        while (TXIF == 0); // Wait till the transmitter register becomes empty
        TXIF = 0; // Clear transmitter flag
        TXREG = a; // load the char to be transmitted into transmit reg
    }

    unsigned char rx() {
        while (!RCIF);
        RCIF = 0;
        return RCREG;
    }

 void txstr(unsigned char *s) {
        while (*s) {
            tx(*s++);

            __delay_us(10);
        }

    }
Posted on Leave a comment

Interface LCD 16×2 Character in 4-Bit Mode with PIC16F877A

To save the number of pins of the microcontroller we interface the character LCD in 4-bit mode.

If we take a look at the PINS of the character LCD

[ VSS | VDD | V0 | R/S | R/W | E | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | A | K ]

If we were to use this LCD in 8-bit mode we have to use 8 GPIO pins for the DATA (D0-D7) and three GPIO Pins for control(R/S, R/W, E).

But in 4-bit mode, we use three control pins and only four data pins D4-D7 .

Schematics

Schematics diagram for Home Automation using Bluetooth

In the above schematic, I have interfaced the LCD in 4-bit mode.

LCD BACKLIGHT
I have connected a 220 ohm resistor to the K pin; since my lcd module has a 100 ohm resistor on board. It is labeled R8. So 100 + 220 = 320 ohm resistance total.
If the LED takes approx. 3V forward volage then the current will be given by equation
Current = (VCC – Forward Voltage) / Total resistance
=> (5v-3v)/320ohm = 2v / 320 ohm = 0.00625 A = 6.25 mA(approx.)

LCD Command

You can make your own commands using this table shown below.

CODE

main.hLCD_16x2.hboard.h
/* 
 * File:   main.c
 * Author: abhay
 *
 * Created on July 14, 2023, 12:05 AM
 */

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#define _XTAL_FREQ 16000000
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "board.h"
#include "LCD_16x2.h"
#include<string.h>

char GPIO_init(void);

/*
 * 
 */
int main(int argc, char** argv) {
    GPIO_init();
    InitLCD(); // Initialize LCD in 8bit mode

    //const char *msg = "Hello World!";
    // const char *msg1 = "Abhay";
    //WriteStringToLCD(msg1);
    char lcd_buff1[16] = "Abhay Kant";
    char lcd_buff2[16] = "Kant";
    
    
    lcd_set_cursor(1, 1);
    WriteDataToLCD('E');
    WriteDataToLCD('X');
    WriteDataToLCD('A');
    WriteDataToLCD('S');
    WriteDataToLCD('U');
    WriteDataToLCD('B');
    WriteDataToLCD('.');
    WriteDataToLCD('C');
    WriteDataToLCD('O');
    WriteDataToLCD('M');

    lcd_set_cursor(2, 1);
    WriteStringToLCD(lcd_buff1);

    while (1) {

    }

    return (EXIT_SUCCESS);
}

char GPIO_init(void) {
    /*
     * TRIS = Data Direction Register
     * 0 = OUTPUT
     * 1 = INPUT
     * TRISD &= ~(1 << 1); // LED RD1 as OUTPUT
     * TRISD1 = 0; // RD1 as OUTPUT
     */



    LED_PORT_DIR &= ~(1 << 1);
    LED_PIN = 0;

    Relay_1a_DIR &= ~(1 << 0);
    Relay_1a_PIN = 0;

    Relay_1b_DIR &= ~(1 << 2);
    Relay_1b_PIN = 0;

    Relay_2a_DIR &= ~(1 << 4);
    Relay_2a_PIN = 0;

    Relay_2b_DIR &= ~(1 << 5);
    Relay_2b_PIN = 0;

    /*
     * LCD Pins Initialize
     */
    TRISB = 0;
    return 0;
}
/*
 * File:   LCD_16x2.h
 * Author: abhay
 *
 * Created on July 16, 2023, 6:21 PM
 */

#ifndef LCD_16X2_H
#define	LCD_16X2_H

#include "board.h"


#ifdef	__cplusplus
extern "C" {
#endif
    /*
     *Sr.No.	Hex Code	Command to LCD instruction Register
        1       01          Clear display screen
        2       02          Return home
        3       04          Decrement cursor (shift cursor to left)
        4       06          Increment cursor (shift cursor to right)
        5       05          Shift display right
        6       07          Shift display left
        7       08          Display off, cursor off
        8       0A          Display off, cursor on
        9       0C          Display on, cursor off
        10  	0E          Display on, cursor blinking off
        11  	0F          Display on, cursor blinking on
        12      10          Shift cursor position to left
        13      14          Shift the cursor position to the right
        14      18          Shift the entire display to the left
        15      1C          Shift the entire display to the right
        16      80          Force cursor to the beginning ( 1st line)
        17  	C0          Force cursor to the beginning ( 2nd line)
        18  	38          2 lines and 5×7 matrix
     */
    // Define Pins

#define LCD_RS         RB1     // RS pin for LCD
#define LCD_RW         RB2     // RS pin for LCD
#define LCD_E          RB3     // Enable pin for LCD
#define LCD_Data_Bus_D4    RB4    // Data bus bit 4
#define LCD_Data_Bus_D5    RB5    // Data bus bit 5
#define LCD_Data_Bus_D6    RB6    // Data bus bit 6
#define LCD_Data_Bus_D7    RB7    // Data bus bit 7
    // Define Pins direction registrers
#define LCD_E_Dir           TRISB3
#define LCD_RS_Dir          TRISB1
#define LCD_RW_Dir          TRISB2
#define LCD_Data_Bus_Dir_D4     TRISB4
#define LCD_Data_Bus_Dir_D5     TRISB5
#define LCD_Data_Bus_Dir_D6    TRISB6
#define LCD_Data_Bus_Dir_D7   TRISB7
    // Constants
#define E_Delay       1000
    // Function Declarations
    void WriteCommandToLCD(unsigned char);
    void WriteDataToLCD(char);
    void InitLCD(void);
    void WriteStringToLCD(const char*);
    void ClearLCDScreen(void);

    void ToggleEpinOfLCD(void) {
        LCD_E = 1; // Give a pulse on E pin
        __delay_us(E_Delay); // so that LCD can latch the
        LCD_E = 0; // data from data bus
        __delay_us(E_Delay);
    }

    void WriteCommandToLCD(unsigned char Command) {
        LCD_RS = 0; // It is a command
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= (Command & 0xF0); // Write Upper nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= ((Command << 4)&0xF0); // Write Lower nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
    }

    void WriteDataToLCD(char LCDChar) {
        LCD_RS = 1; // It is data
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= (LCDChar & 0xF0); // Write Upper nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= ((LCDChar << 4)&0xF0); // Write Lower nibble of data
        ToggleEpinOfLCD(); // Give pulse on E pin
    }

    void InitLCD(void) {
        // Firstly make all pins output
        LCD_E = 0; // E  = 0
        LCD_RS = 0; // RS = 0
        LCD_Data_Bus_D4 = 0; // Data bus = 0
        LCD_Data_Bus_D5 = 0; // Data bus = 0
        LCD_Data_Bus_D6 = 0; // Data bus = 0
        LCD_Data_Bus_D7 = 0; // Data bus = 0
        LCD_E_Dir = 0; // Make Output
        LCD_RS_Dir = 0; // Make Output
        LCD_RW_Dir = 0;
        LCD_RW = 0;
        LCD_Data_Bus_Dir_D4 = 0; // Make Output
        LCD_Data_Bus_Dir_D5 = 0; // Make Output
        LCD_Data_Bus_Dir_D6 = 0; // Make Output
        LCD_Data_Bus_Dir_D7 = 0; // Make Output
        ///////////////// Reset process from datasheet //////////////
        __delay_ms(40);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x30; // Write 0x3 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_ms(6);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x30; // Write 0x3 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_us(300);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x30; // Write 0x3 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_ms(2);
        PORTB &= 0x0F; // Make Data pins zero
        PORTB |= 0x20; // Write 0x2 value on data bus
        ToggleEpinOfLCD(); // Give pulse on E pin
        __delay_ms(2);
        /////////////// Reset Process End ////////////////
        WriteCommandToLCD(0x28); //function set
        WriteCommandToLCD(0x0c); //display on,cursor off,blink off
        WriteCommandToLCD(0x01); //clear display
        WriteCommandToLCD(0x06); //entry mode, set increment
        WriteCommandToLCD(0x0e); //display on,cursor on,blink off
        WriteCommandToLCD(0x0f); //display on,cursor on,blink on
    }

    void WriteStringToLCD(const char *s) {
        while (*s) {
            WriteDataToLCD(*s++); // print first character on LCD
        }
    }

    void ClearLCDScreen(void) // Clear the Screen and return cursor to zero position
    {
        WriteCommandToLCD(0x01); // Clear the screen
        __delay_ms(2); // Delay for cursor to return at zero position
    }

    void lcd_set_cursor(uint8_t row, uint8_t col) {
        if (row == 1) {
            WriteCommandToLCD(0x80 | (col - 1));
        } else if (row == 2) {
            WriteCommandToLCD(0xC0 | (col - 1));
        }
    }

#ifdef	__cplusplus
}
#endif

#endif	/* LCD_16X2_H */

/* 
 * File:   board.h
 * Author: abhay
 *
 * Created on July 14, 2023, 12:41 AM
 */

#ifndef BOARD_H
#define	BOARD_H

#ifdef	__cplusplus
extern "C" {
#endif
/**
 * LED 
 * color: RED
 * @param PORT: D
 * @param PIN: 1
 */
#define LED_PORT_DIR TRISD
#define LED_Port PORTD
#define LED_PIN PORTDbits.RD1
    
#define Relay_1a_DIR TRISD
#define Relay_1a_Port PORTD
#define Relay_1a_PIN PORTDbits.RD0

#define Relay_1b_DIR TRISD    
#define Relay_1b_Port PORTD
#define Relay_1b_PIN PORTDbits.RD2

#define Relay_2a_DIR TRISD
#define Relay_2a_Port PORTD
#define Relay_2a_PIN PORTDbits.RD4

#define Relay_2b_DIR TRISD
#define Relay_2b_Port PORTD
#define Relay_2b_PIN PORTDbits.RD5

#ifdef	__cplusplus
}
#endif

#endif	/* BOARD_H */

Posted on Leave a comment

How to Control the GPIO of PIC16F877A using MPLAB X IDE

The PIC16F877A microcontroller is a popular choice for embedded systems development due to its versatility and ease of use. One of the essential aspects of working with microcontrollers is controlling General Purpose Input/Output (GPIO) pins. In this blog post, we will explore how to control the GPIO pins of the PIC16F877A using MPLAB X IDE, a powerful Integrated Development Environment.

Prerequisites:
To follow along with this tutorial, you will need the following:

  1. PIC16F877A microcontroller.
  2. MPLAB X IDE installed on your computer.
  3. MPLAB XC8 Compiler.

Step 1: Create a New Project
Launch MPLAB X IDE and create a new project by navigating to File -> New Project. Select “Microchip Embedded” under “Categories” and “Standalone Project” under “Projects”. Choose the PIC16F877A as the device and specify a name and location for your project. Click “Finish” to create the project.

Step 2: Configure the GPIO Pins
To control the GPIO pins, we need to configure them as inputs or outputs. In the project window, open the “main.c” source file. Locate the main function and add the necessary code to configure the GPIO pins.

To set a pin as an output, use the TRISx register, where x represents the port name (A, B, C, etc.). For example, to set RB0 as an output pin, use the following code:

TRISBbits.TRISB0 = 0; // Set RB0 as an output pin

To set a pin as an input, use the same TRISx register and set the corresponding bit to 1. For example, to set RA2 as an input pin, use the following code:

TRISAbits.TRISA2 = 1; // Set RA2 as an input pin

Step 3: Control the GPIO Pins Once the GPIO pins are configured, we can control their state by manipulating the corresponding PORT registers. To set an output pin high (logic level 1), write 1 to the corresponding bit in the PORT register. For example, to set RB0 high, use the following code:

PORTBbits.RB0 = 1; // Set RB0 high

To set an output pin low (logic level 0), write 0 to the corresponding bit in the PORT register. For example, to set RB0 low, use the following code:

PORTBbits.RB0 = 0; // Set RB0 low

To read the state of an input pin, you can directly access the corresponding PORT register. For example, to read the state of RD2, use the following code:

if (PORTAbits.RD2 == 1) {
    // RD2 is high
} else {
    // RD2 is low
}

PORTA: It is configured as an analog port by default.
When you want to use it as a Digital I/O port, you have to configure the ADCON1 register.

Demo Code

/* 
 * File:   main.c
 * Author: abhay
 *
 * Created on July 14, 2023, 12:05 AM
 */

// PIC16F877A Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#define _XTAL_FREQ 16000000
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include <stdlib.h>
#include "board.h"

/*
 * 
 */
int main(int argc, char** argv) {
    /*
     * TRIS = Data Direction Register
     * 0 = OUTPUT
     * 1 = INPUT

     */
// Make the Pin 1 of PORT D as output 
      TRISD &= ~(1 << 1); // LED RD1 as OUTPUT
      TRISD1 = 0; // RD1 as OUTPUT
     
    
    // Make the Pin 0 of PORT A as digital input
    ADCON1bits.PCFG0 = 0;
    ADCON1bits.PCFG1 = 1;
    ADCON1bits.PCFG2 = 1;
    ADCON1bits.PCFG3 = 0;
    TRISA0 = 1; // button 
    
    while (1) {
        if((RA0 ) == 1)
        {
            RD1 = 1;
        }
        else {
            PORTD &= ~(1<<1);
        }
       
    }

    return (EXIT_SUCCESS);
}

Step 4: Build and Program the Microcontroller
Now that we have written the code, it’s time to build and program the microcontroller. Connect your PIC16F877A microcontroller to your computer via a suitable programmer/debugger. Ensure that the proper hardware connections are made.

Step 5: Test the GPIO Control
Once the programming is complete, disconnect the programming cable and power the microcontroller using an appropriate power supply. Connect LEDs, switches, or other devices to the configured GPIO pins. Execute the code on the microcontroller and observe the desired behavior of the GPIO pins based on the control logic implemented in your code.

Conclusion:
Controlling the GPIO pins of the PIC16F877A microcontroller using MPLAB X IDE is a fundamental skill in embedded systems development. By following this step-by-step guide, you have learned how to configure the GPIO pins as inputs or outputs and manipulate their states using the appropriate registers. With this knowledge, you can now start building a wide range of projects that involve interacting with the external world through GPIO pins.

Remember to refer to the PIC16F877A datasheet for detailed information on register names, bit assignments, and other specific details related to the microcontroller.

Happy coding and exploring the world of embedded systems!

Posted on Leave a comment

ATmega328PB 16-bit Timer TC3 – Fast PWM Mode

1 millisecond pulse width
50 Hz Frequency
1.5 millisecond pulse width
50 Hz Frequency
2 millisecond pulse width
50 Hz Frequency

I have used this FAST PWM mode to trigger two interrupt service routines.
The timer compares register A sets the frequency.
I have not enabled the output compare pins. since it was used by some other peripheral. So by doing this, I generate a PWM signal by issuing a command in the ISR.

/*
* main.c
*
* Created: 10 July 2023 10:47:23 PM
*  Author: abhay
*/
#define F_CPU 16000000

#include <xc.h>
#include <stdio.h>
#include "util/delay.h"
#include <avr/interrupt.h>
#include "uart.h"
#define LED_ON PORTB |= (1<<5)
#define LED_OFF PORTB &= ~(1<<5)


// Function to send a character via UART
int UART_putchar(char c, FILE *stream) {
	if (c == '\n')
	UART_putchar('\r', stream);  // Add carriage return before newline
	while (!(UCSR0A & (1 << UDRE0))); // Wait for the transmit buffer to be empty
	UDR0 = c; // Transmit the character
	return 0;
}

// Create a FILE structure to redirect the printf stream to UART
FILE uart_output = FDEV_SETUP_STREAM(UART_putchar, NULL, _FDEV_SETUP_WRITE);

ISR(TIMER3_COMPA_vect){
	PORTD |= (1<<6);
}
ISR(TIMER3_COMPB_vect){
	PORTD &= ~(1<<6);
	
}

int main(void)
{
	

	USART_Init();
	// Redirect stdout stream to UART
	stdout = &uart_output;
	DDRB |= (1<<5);	// set Data direction to output for PB5
	LED_OFF; // set output to high
	DDRD |= (1 << 6);	//set PD6 as output
	
	
	/*
	F_CPU = 16000000
	Prescaler = 64
	Frequency = 50Hz
	Period = 0.020 s
	step time = 1/(F_CPU/Prescaler) = 0.000004 s
	number of steps = 0.020/0.000004 = 5000
	*/
	TCNT3 = 0;		// Timer counter initial value = 0
	// Output Compare A value = 5000 or 20 Milli second
	OCR3A = 5000;
	// Output Compare B value = 500 or 2 Milli second 
	OCR3B = 500;	
	// Fast PWM
	TCCR1A |= (1 << WGM31)|(1 << WGM30);	
	//  Prescaler: 64
	TCCR3B |= (1 << WGM32)|(1<<WGM32)|(1 << CS31)|(1 << CS30);	
	// Enable Timer Interrupt for Overflow, Compare match A and Compare Match B
	TIMSK3 |= (1 << OCIE3B)|(1 << OCIE3A)|(1<<TOIE3);	 
	
	// Enable Global Interrupt
	sei();	
	
	while(1)
	{
		OCR3B = 250;//5% 1ms
		_delay_ms(500);
		OCR3B = 375;//7.5% 1.5ms
		_delay_ms(100);
		OCR3B = 500;//10% 2ms
		_delay_ms(500);

	}
	
	
}
Posted on Leave a comment

ATmega328PB 16-bit Timer TC1 – CTC Mode

Timers are essential components in microcontrollers that allow precise timing and synchronization for various applications. The ATmega328PB microcontroller offers several timer/counters, including Timer/Counter 1 (TC1), which is a 16-bit timer with advanced features. In this blog post, I will explore how to utilize Timer 1 in CTC (Clear Timer on Compare Match) mode on the ATmega328PB microcontroller.

Hardware Setup

Before we proceed with the code, ensure you have the necessary hardware setup. You will need an ATmega328PB microcontroller, a 16MHz crystal oscillator, and any additional components required for your specific application. Connect the crystal oscillator to the XTAL1 and XTAL2 pins of the microcontroller to provide a stable clock signal.

UART Communication Initialization

In this example, we will utilize UART communication for debugging or output purposes. Make sure you have already implemented the necessary UART functions or library. The UART initialization code should include setting the baud rate, enabling the transmitter and receiver, and configuring the data format (e.g., number of data bits, parity, and stop bits). We will also redirect the stdout stream to the UART using the stdio.h library, allowing us to use the printf function for UART output.

Timer 1 Configuration in CTC Mode

Let’s dive into the code and configure Timer 1 in CTC mode. Here’s an example code snippet:

/*
* main.c
*
* Created: 7/9/2023 12:47:23 AM
*  Author: abhay
*/
#define F_CPU 16000000

#include <xc.h>
#include <stdio.h>
#include "util/delay.h"
#include <avr/interrupt.h>
#include "uart.h"

// Function to send a character via UART
int UART_putchar(char c, FILE *stream) {
	if (c == '\n')
	UART_putchar('\r', stream);  // Add carriage return before newline
	while (!(UCSR0A & (1 << UDRE0))); // Wait for the transmit buffer to be empty
	UDR0 = c; // Transmit the character
	return 0;
}

// Create a FILE structure to redirect the printf stream to UART
FILE uart_output = FDEV_SETUP_STREAM(UART_putchar, NULL, _FDEV_SETUP_WRITE);
ISR(TIMER1_COMPA_vect){
	printf("2. compare match A\n");
}
ISR(TIMER1_COMPB_vect){
	printf("1. compare match B\n");
}
int main(void)
{
	USART_Init();
	// Redirect stdout stream to UART
	stdout = &uart_output;
	DDRB |= (1<<5);	// set Data direction to output for PB5
	PORTB |= (1<<5); // set output to high
	/*
	* Timer 1 
	* Mode of operation : CTC
	*		When Output Compare A register value equals the 
	*		Timer Counter register (TCNT1) it resets the Timer-Counter-register value
	*		and generates a interrupt. 
	* Only OCR1A will reset the timer counter. 
	* OCR1B can be used to generate a compare match between TCNT1 = 0 and OCR1A
	*
	*/
	TCNT1 = 0;		// Timer counter initial value = 0
	OCR1BH = 0x3D;	// Output Compare B value = 0x3d09 or 1 second
	OCR1BL = 0x09;
	OCR1AH = 0x7a;	// Output Compare A value = 0x7a12 or 2 second
	OCR1AL = 0x12;
	TCCR1B |= (1<<WGM02)|(1 << CS12)|(1 << CS10);	// CTC Prescaler: 1024
	TIMSK1 |= (1 << OCIE1B)|(1 << OCIE1A)|(1<<TOIE1);
	
	sei();	// Enable Global Interrupt
	while(1)
	{

		
		printf("  This is Main:\n");
 		_delay_ms(2500);
	
		//TODO:: Please write your application code
		
	}
}

In this code snippet, we first initialize the UART communication and redirect the stdout stream to the UART output using the FDEV_SETUP_STREAM macro. The UART_putchar function is used to send a character via UART, ensuring that newline characters (\n) are preceded by a carriage return character (\r) for proper line endings.

Next, we configure Timer/Counter 1 (TC1) for CTC mode and set the prescaler to 1024, which divides the clock frequency to generate a suitable timebase. The TCCR1A and TCCR1B registers are set accordingly.

We then set the compare values (OCR1A and OCR1B) to determine the time intervals at which we want to generate interrupts. In this example, OCR1A is set for 2 second delay, and OCR1B is set for approximately 1 seconds delay.

Finally, we enable the Timer/Counter TC1 compare match interrupts (OCIE1A and OCIE1B) using the TIMSK1 register, and we enable global interrupts with the sei() function.

Interrupt Service Routines (ISRs)

The code snippet defines two Interrupt Service Routines (ISRs): TIMER1_COMPA_vect and TIMER1_COMPB_vect. These ISRs will be executed when a compare match occurs for Output Compare A and Output Compare B, respectively. In this example, we use these ISRs to print messages to the UART output. You can modify these ISRs to perform any desired actions based on your specific application requirements.

Putting It All Together

Once you have set up the UART communication, configured Timer 1 in CTC mode, and defined the necessary ISRs, you can utilize the precise timing capabilities of Timer 1 in your main program loop. Use the printf function to output information via UART, and the compare match interrupts will handle the precise timing events.

while (1) {
   printf("  This is Main:\n");
 		_delay_ms(2500);
    // Additional code and operations
    // ...
}

In the above example, the main program loop will execute continuously, printing “This is the main program loop” every 1 second using the printf function. The _delay_ms function provides a delay of 2500 milliseconds (2.5 second) between each iteration of the loop.

Conclusion

Utilizing Timer 1 in CTC mode on the ATmega328PB microcontroller provides precise timing capabilities for various applications. By configuring Timer 1, setting compare match values, and utilizing compare match interrupts, you can achieve accurate timing control in your embedded systems. When combined with UART communication, you can easily monitor and debug your code by printing relevant information via the UART interface.

Remember to consult the ATmega328PB datasheet and relevant documentation for more details on Timer 1, CTC mode, and other timer features. Ensure that you have correctly configured your hardware setup, including the crystal oscillator and UART connection, to match the code requirements.

Using Timer 1 in CTC mode

with UART communication opens up a range of possibilities for precise timing and debugging capabilities in your projects. Experiment with different compare match values and integrate this functionality into your applications to enhance timing accuracy and control.

Posted on Leave a comment

ATmega328PB 8-Bit Timer TC0 – Normal Mode

The simplest mode of operation is the Normal mode (WGM0[2:0] = 0x0). In this mode, the counting direction is always up (incrementing), and no counter clear is performed. The counter simply overruns when it passes its maximum 8-bit value (TOP=0xFF) and then restarts from the bottom (0x00). In Normal mode operation, the Timer/Counter Overflow flag (TOV0) will be set in the same clock cycle in which the TCNT0 becomes zero. In this case, the TOV0 flag behaves like a ninth bit, except that it is only set, not cleared. However, combined with the timer overflow interrupt that automatically clears the TOV0 flag, the timer resolution can be increased by software. There are no special cases to consider in the Normal
mode, a new counter value can be written any time.

The output compare unit can be used to generate interrupts at some given time. Using the output compare to generate waveforms in Normal mode is not recommended since this will occupy too much of the CPU time.

The counter will always count from 0 to 255 and then after 255 it will set the overflow bit and start the counting from 0.

Without Interrupt

We can use this to generate delays. But This is a type of blocking code.

/*
 * main.c
 *
 * Created: 7/9/2023 12:47:23 AM
 *  Author: abhay
 */ 
#define F_CPU 16000000UL
#include <xc.h>

#include <util/delay.h>
void T0delay();
int main(void)
{
	DDRB |= (1<<5);
	PORTB |= (1<<5);
    while(1)
    {
		PORTB |= (1<<PINB5);
		T0delay();
		PORTB &= ~(1<<PINB5);
                T0delay();
		//TODO:: Please write your application code 
		
    }
}
void T0delay()
{
	/*
	F_CPU/prescaler = clock for timer
	1/clock for timer = timer step time
	for 1 second the timer will take : 1/timer step time
	
	16000000/1024 = 15625 clock
	1/15625 = 0.000064 = 64us	(counter step size)
	64us x 256 = 0.016384 sec (0verflow time)
	64us x 255 - tcnt +1 = 0.016384 sec (0verflow time)
		64us x (255 - tcnt +1 )= x
		tcnt = x / 64us ) -256
		
	for 1 sec Delay
	=> 1/0.016384 sec = 61.03515625
	Taking only integer 
	so 61 overflows needs to occur for 0.999424 Seconds
	1-0.999424 = 0.000576 seconds
	0.000576/ (counter step size) = required steps 
	0.000576/0.00064 = 9 steps
	Note: this will be close to 1 second. but it will be longer due to overhead added by the instructions.
	*/
	for(int i = 0; i< 61;i++){
	TCNT0 = 0;
	TCCR0A = 0;
	TCCR0B |= (1<<CS00)|(1<<CS02);	//prescaler 1024
	while( (TIFR0 & 0x1) == 0);	// Wait for overflow flag
	TCCR0B = 0;
	TIFR0 = 0x1;
	}
	//9 steps timer code
	TCNT0 = 255-9;
	TCCR0A = 0;
	TCCR0B |= (1<<CS00)|(1<<CS02);	//prescaler 1024
	while( (TIFR0 & 0x1) == 0);	// Wait for overflow flag
	TCCR0B = 0;
	TIFR0 = 0x1;
}

Normal Mode with Interrupt

#define F_CPU 16000000UL
#include <xc.h>
#include <avr/interrupt.h>
ISR(TIMER0_OVF_vect){
	PORTB ^= (1<<PINB5);   // Toggle the GPIO
}
int main(void)
{
	
	DDRB |= (1<<5);	// set Data direction to output for PB5
	PORTB |= (1<<5);    // set output to high
	
	TCNT0 = 0;
	TCCR0A = 0;     // Normal Mode
	TCCR0B |= (1<<CS00)|(1<<CS02);	//prescaler 1024
	TIMSK0 |= (1 << TOIE0);	// Overflow Interrupt Enable Bit
	
	sei();	// Enable Global Interrupt
	while(1)
	{
		//TODO:: Please write your application code
		
	}
}

Posted on Leave a comment

How to use S109AFTG Microstep Driver with ATmega328PB Programmed using Microchip Studio

When I opened the case. I found a PCB which is screwed to a big heatsink. I unscrewed the bolts and saw that there is S109AFTG. The IC is sandwiched between the PCB and the heatsink. A small aluminum block is also used for heat transfer between the IC and the heatsink.

It has a different step size which can be selected by the DIP switches.
The motor driver has a maximum of 1/32 step size.

which means 1.8°/ 32 = 0.05625°

360°/ 0.05625° = 6400 steps

So a full rotation will be in 6400 steps.

You will need a power source such as a Switched Mode Power Supply which can supply at least 2 Amps.

If your application needs more torque you will need a power source that can provide a high current without dropping the voltage.

Or you can use the battery for a short duration.

Schematic Diagram

Code

/*
* main.c
*
* Created: 7/4/2023 5:51:21 PM
*  Author: abhay
*/
#define F_CPU 16000000
#include <xc.h>
#include <util/delay.h>
int PUL=PIND6; //define Pulse pin
int DIR=PINB1; //define Direction pin
int ENA=PIND2; //define Enable Pin
#define DirLow PORTB &= ~(1<<DIR)
#define DirHigh PORTB |= (1<<DIR)
#define PulLow PORTD &= ~(1<<PUL)
#define PulHigh PORTD |= (1<<PUL)
#define EnaLow PORTD &= ~(1<<ENA)
#define EnaHigh PORTD |= (1<<ENA)
#define delayus50 _delay_us(50)
int main(void)
{
	DDRB |= (1<<DIR);
	DDRD |= (1<<PUL)|(1<<ENA);
	while(1)
	{
		//TODO:: Please write your application code
		for (int i=0; i<6400; i++)    //Forward 6400 steps
		{
			DirLow;
			EnaHigh;
			PulHigh;
			delayus50;
			PulLow;
			delayus50;
		}
		_delay_ms(5000);
		for (int i=0; i<6400; i++)   //Backward 6400 steps
		{
			DirHigh;
			EnaHigh;
			PulHigh;
			delayus50;
			PulLow;
			delayus50;
		}
		_delay_ms(2000);
	}
}
Posted on Leave a comment

Diodes

  • Diodes are electronic devices that conduct in one direction. Ideally, they have to block the conduction of current in the reverse direction, but in reality, there is always a small leakage current present.
  • Due to the presence of impurities in the diode, it gets hot when a large amount of current is passed through.
  • There are diodes for various applications which focus on a special property of the diode.
    • For example:
      • Zener Diode – Works in reverse bias condition. Provide excellent voltage regulation.
      • Schottky Diode – Has low forward voltage drop and high switching speed. Suitable for high-frequency applications and power supply circuits.
      • Silicon Controlled Rectifier (SCR) – Can handle high current and voltage levels. Used for power switching and motor control applications.
      • Light Emitting Diode (LED) – Emits light when forward biased. Used for lighting and display applications.
      • Tunnel Diode – Exhibits negative resistance. Used in microwave oscillators, amplifiers, and detectors.
      • Avalanche Diode – Can withstand high reverse voltage and exhibits avalanche breakdown. Used for overvoltage protection and voltage regulation.
      • Photodiode – Generates a current when exposed to light. Used in optical communication and sensing applications.
      • These are just a few examples, and there are many more types of diodes available for various applications.

The following properties should be looked at in a datasheet. There may be additional details but these are the minimum.

  • VF is the voltage drop across the diode when it is conducting current in the forward direction. For example, a silicon diode may have a VF of around 0.7V.
  • IF is the maximum current that the diode can handle without being damaged. For example, a diode rated for 1A can handle a maximum current of 1A flowing through it.
  • VR is the maximum reverse voltage that the diode can withstand before breakdown. For example, a diode with a VR of 100V can withstand a reverse voltage of up to 100V before it starts conducting in the reverse direction.
  • PD is the maximum power that the diode can safely dissipate without getting damaged. For example, a diode with a PD of 500mW can safely dissipate up to 500mW of power without getting damaged.
  • Tj is the maximum temperature that the junction of the diode can reach without getting damaged. For example, a diode with a Tj of 150°C can safely operate at a maximum temperature of 150°C.
  • trr is the time taken by the diode to switch from forward conduction to reverse blocking mode. For example, a diode with a trr of 50ns will take 50ns to switch from forward conduction to reverse blocking mode.
  • The package type and dimensions specify the physical size and shape of the diode and are usually given in the mechanical drawing section of the datasheet. For example, a diode may be packaged in a through-hole or surface-mount package with specific dimensions.

Electronic Diodes and Their Part Numbers

Rectifier Diodes

  • Small Signal Diode: 1N4148, 1N914
  • Schottky Diode: BAT54, BAT85
  • Silicon Controlled Rectifier: TYN616, C106D
  • PIN Diode: HP5082-2810, HSMP-386L

Zener Diodes

  • Zener Diode: 1N4728A, 1N5349B

LED and Laser Diodes

  • Light Emitting Diode (LED): 5mm Red LED, 3mm Green LED
  • Laser Diode: 650nm Red Laser, 405nm Blue Laser

Special Function Diodes

  • Tunnel Diode: 1N3716, NTT406AB
  • Varactor Diode: BB112, BB204
  • Transient Voltage Suppression Diode: P6KE36A, 1.5KE200A
  • Avalanche Diode: BZX84C5V6, P6KE100CA

Photodiodes

  • PIN Photodiodes: BPW34, SFH205F
  • Avalanche Photodiodes: S8664, C30932EH
  • Schottky Photodiodes: 1N5711, HSMS-2855-BLKG
  • MSM Photodiodes: YT201M, YT202M
  • InGaAs Photodiodes: G9933, G8941

Power Diodes

  • General Purpose Power Diodes: 1N4007, 1N5399
  • Fast Recovery Power Diodes: UF4007, FR107
  • Schottky Barrier Diodes: SB560, SB5100
  • Ultrafast Recovery Power Diodes: UF5404, UF5408
  • Super Barrier Diodes: SB540, SB570
  • Avalanche Diodes: MUR1100E, 1N4937GP
  • TVS Diodes (Transient Voltage Suppressor): P6KE15CA, 1.5KE400A
Posted on Leave a comment

What are AVR microcontrollers?

AVR microcontrollers, developed by Atmel Corporation (now Microchip Technology), are designed for embedded applications that require low power consumption, high performance, and a small footprint. The AVR family includes a range of microcontrollers, each with varying processing power, memory capacity, and peripheral features.

  • ATmega series: This popular series of 8-bit AVR microcontrollers offers up to 256KB of flash memory, 16-channel 10-bit ADC, and up to 86 general-purpose I/O pins.
    • Example: ATmega328P, which is commonly used in the Arduino Uno board. It has 32KB of flash memory, 2KB of SRAM, and 1KB of EEPROM.
  • ATtiny series: This low-power, low-cost series of 8-bit AVR microcontrollers is ideal for simple applications that require basic processing and control functions. They typically have less flash memory and fewer peripheral features compared to the ATmega series.
    • Example: ATtiny85, which is commonly used in the Digispark development board. It has 8KB of flash memory, 512 bytes of SRAM, and 512 bytes of EEPROM.
  • ATxmega series: This series of 8/16-bit AVR microcontrollers offers higher processing power, more memory, and advanced features such as DMA, DAC, and RTC.
    • Example: ATxmega128A1, which has 128KB of flash memory, 8KB of SRAM, and 2KB of EEPROM.
  • AT91SAM series: This series of ARM-based microcontrollers combines the low power consumption and high performance of the AVR architecture with the advanced features and processing power of the ARM architecture.
    • Example: AT91SAM9G25, which is based on the ARM926EJ-S core and has 64KB of SRAM, 32KB of ROM, and a variety of peripheral features such as Ethernet and USB.
  • AVR32 series: This series of 32-bit AVR microcontrollers offers high processing power and advanced features such as floating-point processing, DMA, and high-speed connectivity.
    • Example: AVR32 UC3A0512, which has 512KB of flash memory, 64KB of SRAM, and a variety of peripheral features such as Ethernet, USB, and CAN.

Overall, AVR microcontrollers are versatile and widely used in a variety of applications, such as automotive electronics, home automation, industrial automation, robotics, and consumer electronics. They can be programmed using a variety of programming languages and development environments, including C, C++, Assembly, and Arduino.

Comprehensive Atmel Microcontroller Series

AT90 Series:
AT90CAN128	AT90CAN32	AT90CAN64
AT90PWM1	AT90PWM161	AT90PWM2
AT90PWM261	AT90PWM2B	AT90PWM3
AT90PWM316	AT90PWM3B	AT90PWM81
AT90USB1286	AT90USB1287	AT90USB162
AT90USB646	AT90USB647	AT90USB82

ATmega Series:
ATmega128	ATmega1284	ATmega128A
ATmega128x	ATmega16	ATmega1608
ATmega1609	ATmega162	ATmega164
ATmega164P	ATmega165A	ATmega165P
ATmega165PA	ATmega168	ATmega168A
ATmega168P	ATmega168PA	ATmega168PB
ATmega169A	ATmega169P	ATmega169PA
ATmega16A	ATmega16M1	ATmega16U2
ATmega16U4	ATmega256x	ATmega32
ATmega3208	ATmega3209	ATmega324
ATmega324P	ATmega324PB	ATmega325
ATmega3250	ATmega3250A	ATmega3250P
ATmega3250PA	ATmega325A	ATmega325P
ATmega325PA	ATmega328	ATmega328P
ATmega328PB	ATmega329	ATmega3290
ATmega3290A	ATmega3290P	ATmega3290PA
ATmega329A	ATmega329P	ATmega329PA
ATmega32A	ATmega32M1	ATmega32U2
ATmega32U4	ATmega406	ATmega48
ATmega4808	ATmega4809	ATmega48A
ATmega48P	ATmega48PA	ATmega48PB
ATmega48V	ATmega64	ATmega640
ATmega644	ATmega644P	ATmega645
ATmega6450	ATmega6450A	ATmega6450P
ATmega645A	ATmega645P	ATmega649
ATmega6490	ATmega6490A	ATmega6490P
ATmega649A	ATmega649P	ATmega64A
ATmega64M1	ATmega8		ATmega808
ATmega809	ATmega8515	ATmega8535
ATmega88	ATmega88A	ATmega88P
ATmega88PA	ATmega88PB	ATmega8A
ATmega8U2	ATtiny10	ATtiny102

ATtiny Series:
ATtiny104	ATtiny12	ATtiny13
ATtiny13A	ATtiny1604	ATtiny3217
ATtiny1606  	ATtiny3217	ATtiny1607
ATtiny3217	ATtiny1614  	ATtiny3217
ATtiny1616  	ATtiny3217	ATtiny1617
ATtiny3217	ATtiny1634	ATtiny167
ATtiny20	ATtiny202	ATtiny212
ATtiny214	ATtiny2313	ATtiny24
ATtiny25	ATtiny26	ATtiny28L
ATtiny3216	ATtiny3217	ATtiny3217
ATtiny4
ATtiny40	ATtiny402	ATtiny406
ATtiny412	ATtiny414	ATtiny416
ATtiny417	ATtiny43	ATtiny4313
ATtiny44	ATtiny441	ATtiny45
ATtiny48	ATtiny5		ATtiny806
ATtiny807	ATtiny814	ATtiny816
ATtiny817	ATtiny828	ATtiny84
ATtiny841	ATtiny85	ATtiny87
ATtiny88	ATtiny9		ATtinyx04
ATtinyx61	ATtinyx61A	XMEGA A1

XMEGA Series:
XMEGA A1U	XMEGA A3	XMEGA A3B
XMEGA A3BU	XMEGA A3U	XMEGA A4
XMEGA A4U	XMEGA B1	XMEGA B3
XMEGA C3	XMEGA C4	XMEGA D3