Doing calculations in microcontroller based project is not an uncommon thing. That’s what CPU is for, isn’t it? However, all these calculations done by MCU are pre-written as a part of code – which means these formulae are part of the program written by programmer and cannot be changed or altered for each new calculation, if the situation demands so.
What if your system has to solve equations on the fly? What if the user wants to define an equation to be evaluated and MCU has to perform the task based on that? Well, here comes handy a Postfix Notation to solve any type of equations in run-time that too without changing any source code. This method not only helps in solving trivial expressions like 3 + 4 but also can handle anything that involves parenthesis or algebraic operator precedence.
Instead of explaining just the Postfix Notation, algorithm and overall method, we will also build a somewhat basic calculator based on this technique using Atmel’s AVR microcontroller.
About Postfix Notation
Postfix is a mathematical notation wherein every operator follows all of its operands. In computer science, Postfix Notation is often used in stack-based and concatenative programming languages. It is also common in dataflow and pipeline-based systems, including UNIX pipelines.
The method was first introduced in 1954 and was first used in desktop calculators by HP during 1963. However, now almost all the calculators deploy this methodology to perform calculations of user inputs.
In Postfix Notation the operators follow their operands; for instance, to add 4 and 6, one would write 4 6 + rather than 4 + 6. If there are multiple operations, the operator is given immediately after its second operand; so the expression written 1 ? 4 + 6 in conventional notation – would be written as 1 4 ? 6 + in Postfix. That means, first subtract 4 from 1 and then add 6 to that.
An advantage of Postfix is that it obviates the need for parentheses that are required by normal notations and thus removes ambiguity from the formulae. For instance, 1 ? 4 * 6 can also be written 1 ? (4 * 6) and it is quite different from (1 ? 4) * 6. In Postfix, the former could be written 1 4 6 * ?, which unambiguously means 1 (4 6 *) ? whereas the latter could be written 1 4 – 6 * or 6 1 4 – *. In either case, you would see that operators with higher priority would come on far right (refer to BoDMAS rule we learnt in school days). As the notation result is always context-free, once an equation is converted in Postfix Notation, it becomes easier for a computer to evaluate the same using outside-in evaluation sequence.
Using Postfix in Program Code
While using and converting normal formulae in Postfix Notation on paper would be easier putting that in software code presents many challenges. Typically method to do so would be to construct an abstract syntax tree and then perform simple post-order traversal of that tree. Once the notation is created it obiously needs to be evaluated and to carry out outside-in evaluation sequence, use of stack is necessary. This means our processor should have sufficient stack (and hence the RAM) available to allow our program utilize that stack and arrive at the final logical result. It must be noted that with limited or small stack size program ability becomes limited, too.
Postfix Algorithm
As mentioned earlier, the method is stack and queue dependant and uses stack for storing functions (aka operators) and a simple Queue to hold numbers (aka operands). Refer to Figure 1 for algorithm steps. Due to the nature of operations performed in this algorithm, it is also called as Shunting Yard Algorithm since the operation resembles to railroad shunting yard methodology. Table 1 depicts operator precedence alongwith associvity. This precedence is usual algebraic precedence which follows BoDMAS rule.
This algorithm has been used to convert user input into Postfix Notation and then evaluate them later on. Table 2 shows step by step evaluation of a regular expression using this algorithm.
Once parsing is done, rest of the part is much easier to perform.
1. For evaluating the answer, we perform following steps…
2. Initialize an empty stack.
3. Scan the Postfix Notation string from left to right.
4. If the token read is operand, push it into stack; else if the token read is operator (that would mean there are at least two operands already present in the stack)
i. Pop these two operands from stack.
ii. Perform the operation as per the operator.
iii. Push the results back into the stack.
5. Once the string scanning is complete – there would be only one element present in the stack which is final answer.
For example to evaluate the notation as resulted in earlier step, following is the step-by-step output…
Refer to the last step # 11, where you would see just one number remaining in stack and Postfix string is over. The last number remaining in evaluation is our final result. That means…
3 + 2 * 4 / ( 1 – 5 ) à in Postfix Notation form à 3 2 4 * 1 5 – / + à evaluates to 1
Check the answer using calculator (if needed), the answer is correctly evaluated. You may try a few more expressions in similar fashion and validate the conversion and evaluation algorithms. Now that the core logic and algorithm has been developed and validated, we can build requisite hardware and software to make it a reality.
Hardware Set up and Prototype
Hardware Setup
For faster development purpose ATmega168 Development Stick has been used which is available with EFY associates’ Kits-N-Spares. Figure 2 shows the schematic of hardware and wiring setup.
The ATmega168 Development Stick comes with built-in bootloader and has direct connections for LCD with backlight control. Stick can directly communicate with PC over USB connection with the help of onboard Serial-to-USB converter.
As shown in Figure 2, LCD of 16×2 size has been connected to the development stick on Port B. It also has onboard USART to USB converter which has been utilized to connect the stick directly to PC using Type A – Type Mini B USB connector. This setup helps in sending the commands to MCU over USB from PC using terminal utility program such as Windows Hyperterminal or using Bray’s serial terminal utility.
Besides the software code or logic and algorithm, hardware setup is pretty common in terms of interfacing LCD in 4-bit mode and connecting the board with PC using USB port. When connected to PC power supply connection is not required since stick draws supply from PC and current consumption is well below 80mA with LCD backlight on; in case of LCD backlight off, current consumption falls well below 10mA.
Software Code
Overall flowchart of entire software is shown above. As shown, software first reads the input string from USART terminal; scans it and converts it into processable string. First the string needs to be converted into proper evaluatable string; this is due to the fact that we are receiving a text string and we need to extract numbers from it. Then it gets converted into Postfix Notation using aforementioned algorithm within a separate function. Output of this function is a Postfixed string which then gets evaluated for final result and this result is sent to computer via USART terminal as well as displayed on LCD screen. Software code also implements Stack and Queue type data-structure which are used for conversion and calculation of output.
Working & Prototype Results
The development stick used is built around well know AVR microcontroller chip ATmega168, 32 pin TQFP version. The development stick itself has a compact size and thus fits easily on the backside of 16×2 LCD. The MCU used (ATmega168) has 16kb program Flash, 1kb SRAM and 512kb of EEPROM. Having 1kb SRAM enables evaluation of medium-complexity expressions using Postfix Notation. In case of higher complexity requirements MCUs with higher RAM capacity such as Atmega328P can be used.
The program code is written using Atmel Studio 6.0 and the hex file is burnt into MCU using pre-installed boot-loader in ATmega168 Development Stick with the help of AVRdude command line software. Screenshot 1 shows photograph of actual prototype alongwith the output shown on LCD screen.
Future Augmentation of The Concept
Besides building a calculator prototype for hobby or learning use; the most significant use I could imagine is to develop a mathematical brain for a bigger MCU based system. Where this MCU could act as calculator for system’s master MCU and all the communication can take place over USART, I2C or SPI channel. This is also called as distributed or parallel computing.
One of the other applications (under development) where this would be useful is – data or image compression utility for hardware systems. In such a system, MCU can read a file or data and build a smaller expression or equation that depicts overall data and then store that equation rather than storing entire file or data. During decryption or read-back process, the equation can be evaluated and data can be reproduced back with ease.
In this article, we have used only a few basic operators for calculation purpose; however, many unary operators or functions such as LOG, SIN, COS, exponent, etc. are yet to be added to the logic. Adding these operations would definitely increase capability of the MCU. Adding few more type of operators means more memory, stack and queue requirement and should be taken care of appropriately.
Although this article uses AVR MCU for implementation of Postfix logic, any other MCU that can satisfy hardware requirements can be used for this purpose with minor modifications in the program syntax and other code elements.
Project Source Code
Project Source Code
###
********************Header File********************
#include "avr/io.h"#include "avr/pgmspace.h"#include "avr/boot.h"#include "avr/wdt.h"#include "avr/interrupt.h"#include "avr/eeprom.h"#include "avr/sleep.h"#include "stdio.h"#include "stdarg.h"#include "util/delay.h"#define LOBYTE(_W_) (_W_ & 0x0F)#define HIBYTE(_W_) ((_W_ >> 4) & 0x0F)#define OUTPUT_PORT 0xFF#define OUTPUT_PIN 1#define INPUT_PORT 0x00#define INPUT_PIN 0enum {OFF, ON, HIGH_Z};enum {FALSE, TRUE};typedef struct{unsigned char b0:1; unsigned char b1:1; unsigned char b2:1; unsigned char b3:1;unsigned char b4:1; unsigned char b5:1; unsigned char b6:1; unsigned char b7:1;} bits;#define PORT_B (*(volatile bits *) & PORTB)#define PIN_B (*(volatile bits *) & PINB)#define DDR_B (*(volatile bits *) & DDRB)#define PORT_C (*(volatile bits *) & PORTC)#define PIN_C (*(volatile bits *) & PINC)#define DDR_C (*(volatile bits *) & DDRC)#define PORT_D (*(volatile bits *) & PORTD)#define PIN_D (*(volatile bits *) & PIND)#define DDR_D (*(volatile bits *) & DDRD)#if defined (_AVR_IOM16_H_) || (_AVR_IOM32_H_)#define __UCSRB UCSRB#define __TXEN TXEN#define __RXEN RXEN#define __UCSRC UCSRC#define __UCSZ0 UCSZ0#define __UCSZ1 UCSZ1#define __URSEL URSEL#define __UBRRL UBRRL#define __UBRRH UBRRH#define __SPMEN SPMEN#define __BLBSET BLBSET#define __UCSRA UCSRA#define __UDRE UDRE#define __RXCIE RXCIE#define __RXC RXC#define __UDR UDR#elif defined (_AVR_IOM168P_H_) || (_AVR_IOM168_H_) || (_AVR_IOM328P_H_) || (_AVR_IOM88_H_)#define __UCSRB UCSR0B#define __TXEN TXEN0#define __RXEN RXEN0#define __UCSRC UCSR0C#define __UCSZ0 UCSZ00#define __UCSZ1 UCSZ01#define __URSEL UMSEL01#define __UBRRL UBRR0L#define __UBRRH UBRR0H#define __SPMEN SELFPRGEN#define __BLBSET BLBSET#define __UCSRA UCSR0A#define __UDRE UDRE0#define __RXC RXC0#define __RXCIE RXCIE0#define __UDR UDR0#endif#if defined (LCD_PORT)#define _LCD_DATA_ LCD_PORT(PORT)#define _LCD_DDR_ LCD_PORT(DDR)#define LCD_BK LCD_PORT(PORT_).b7#define RS LCD_PORT(PORT_).b5#define EN LCD_PORT(PORT_).b4#define LCD_D4 LCD_PORT(PORT_).b3#define LCD_D5 LCD_PORT(PORT_).b2#define LCD_D6 LCD_PORT(PORT_).b1#define LCD_D7 LCD_PORT(PORT_).b0#define LCD_Row1 0x80#define LCD_Row2 0xC0#define LCD_Row3 0x94#define LCD_Row4 0xD4#define LCD_EN() {EN = 1; _delay_ms(5); EN = 0;}#define Write_Cmd(Byte) _nWrite(0, Byte)#define Write_Char(Byte) _nWrite(1, Byte)#define Clear_LCD() Write_Cmd(0x01)#define Next_Line() Write_Cmd(0xC0)#define LCD_Backlight(x) LCD_BK = x#if defined (E1004)inline void Write_Nibble(unsigned char Nibble){LCD_D4 = (Nibble >> 0) & 1;LCD_D5 = (Nibble >> 1) & 1;LCD_D6 = (Nibble >> 2) & 1;LCD_D7 = (Nibble >> 3) & 1;LCD_EN();}#endifvoid _nWrite(unsigned char Type, unsigned char xByte){RS = Type;Write_Nibble(HIBYTE(xByte));RS = Type;Write_Nibble(LOBYTE(xByte));_delay_us(50);}void Init_LCD(){_LCD_DDR_ = OUTPUT_PORT;_delay_ms(25);Write_Cmd(0x33);_delay_us(200);Write_Cmd(0x32);_delay_us(200);Write_Cmd(0x28);_delay_us(50);Write_Cmd(0x28);_delay_us(50);Write_Cmd(0x0C);Write_Cmd(0x06);_delay_us(50);Clear_LCD();_delay_us(50);}void _LCD_gotoxy(unsigned char Row, unsigned char Col){switch(Row){case 1: Row = LCD_Row1; break;case 2: Row = LCD_Row2; break;case 3: Row = LCD_Row3; break;case 4: Row = LCD_Row4; break;}Row += --Col;Write_Cmd(Row);}void _LCD_Write(unsigned char *strText){while(*strText){switch(*strText){case'n':case'r':strText++;Next_Line();continue;default :Write_Char(*strText);}strText++;}}void Write_LCD(unsigned char Row, unsigned char Col, char *Str, ...){char String[90];va_list arg_ptr;va_start(arg_ptr, Str);vsprintf(String, Str, arg_ptr);va_end(arg_ptr);if(!Str) return;_LCD_gotoxy(Row, Col);_LCD_Write((unsigned char *)String);}#endif#if defined (USART)#if !defined (USART_BAUDRATE)#define USART_BAUDRATE 9600#endif#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)void Init_USART(){__UCSRB |= _BV(__RXEN) | _BV(__TXEN);__UCSRC |= _BV(__URSEL) | _BV(__UCSZ0) | _BV(__UCSZ1);__UBRRL = BAUD_PRESCALE;__UBRRH = BAUD_PRESCALE >> 8;}inline void TxChar(unsigned char Chr){while((__UCSRA & _BV(__UDRE)) == 0);__UDR = Chr;}inline unsigned char RxChar(){while((__UCSRA & _BV(__RXC)) == 0);return(__UDR);}void RxNchar(unsigned char *Str, unsigned char Size, unsigned char StopChar){do{*Str = RxChar();if(StopChar != 0x00)if(*Str == StopChar)return;Str++;Size--;}while(Size);}void TxNchar(unsigned char *strText){while(*strText){switch(*strText){case'n':case'r':strText++;TxChar(0x0D);TxChar(0x0A);continue;default :TxChar(*strText);}strText++;}}void Write_USART(char *Str, ...){unsigned char String[128];va_list arg_ptr;va_start(arg_ptr, Str);vsprintf((char *)String, Str, arg_ptr);va_end(arg_ptr);TxNchar((unsigned char *)&String);}#endif
********************Calculator File********************#define E1004// Using E1004 type development stick#define F_CPU8000000UL// Internal 8 MHz oscillator#define LCD_PORT(_)_ ## B// LCD connected to PORT B#define USARTON// USART communication over USB is being used#define USART_BAUDRATE19200// At 19200 bps speed#include "Header.h"#define DIV'/'#define MUL'*'#define ADD'+'#define SUB'-'// Structure for equation string stacktypedef struct Char_Stack{unsigned charValue;struct Char_Stack*Next;};// Structure for equation numbers' stacktypedef struct Float_Stack{floatValue;struct Float_Stack*Next;};// Structure for FIFO Queuetypedef struct Float_Q{floatValue;struct Float_Q*Next;};struct Float_Q *Front = NULL, *Rear = NULL;// Adds given no. to the Queueunsigned char AddQ(float Num){struct Float_Q *Temp;Temp = (struct Float_Q*)malloc(sizeof(struct Float_Q));if(Temp == NULL)return 0;else{Temp->Value = Num;Temp->Next = NULL;if(Front == NULL)Rear = Front = Temp;else{Rear->Next = Temp;Rear = Rear->Next;}return 1;}}// Removes first no. from the Queue and returns the value to calling functionfloat DeleteQ(){float Value;struct Float_Q *Temp;if(Front == NULL)return 0;else{Value = Front->Value;Temp = Front;Front = Front->Next;free(Temp);return Value;}}// Pushes a character to character equation stackunsigned char Push_Char(struct Char_Stack **Top, unsigned char ch){struct Char_Stack *Temp;Temp = (struct Char_Stack*)malloc(sizeof(struct Char_Stack));if(Temp == NULL)return 0;else{Temp->Value = ch;Temp->Next = *Top;*Top = Temp;return 1;}}// Pops a character from character equation stack and returns value to calling functionunsigned char Pop_Chr(struct Char_Stack **Top){unsigned char Value;struct Char_Stack *Temp;if(*Top != NULL){Temp = *Top;Value = Temp->Value;*Top = Temp->Next;free(Temp);}elseValue = 0;return Value;}// Checks if the stack is empty and returns TRUE / FALSE accordinglyinline unsigned char IsEmpty(struct Char_Stack **Top){return(*Top == NULL);}// Pushes a number to equation stackunsigned char Push_Dbl(struct Float_Stack **Top, float Num){struct Float_Stack *Temp;Temp = (struct Float_Stack*)malloc(sizeof(struct Float_Stack));if(Temp == NULL)return 0;else{Temp->Value = Num;Temp->Next = *Top;*Top = Temp;return 1;}}// Pops a number from equation stack and returns value to calling functionfloat Pop_Dbl(struct Float_Stack **Top){float Value;struct Float_Stack *Temp;if(*Top != NULL){Temp = *Top;Value = Temp->Value;*Top = Temp->Next;free(Temp);}elseValue = 0;return Value;}// Performs requested operation between two supplied numbers and returns answer to calling functionfloat Binary_Operate(float Operand1, float Operand2, unsigned char Operation){switch(Operation){case DIV: return(Operand1 / Operand2);case MUL: return(Operand1 * Operand2);case ADD: return(Operand1 + Operand2);case SUB: return(Operand1 - Operand2);}return 0;}// Checks if the character supplied is operator or not, returns TRUE / FALSEinline unsigned char IsOperator(unsigned char chr){return(chr == ADD || chr == SUB || chr == MUL || chr == DIV);}// Defines and returns precedence of each operator, higher the precedence higher the number (follows BoDMAS rule)unsigned char Value(unsigned char Opt){switch(Opt){case DIV: return 4;case MUL: return 3;case ADD: return 1;case SUB: return 1;}return 0;}// Applies postfix algorithm to equation string and returns postfixed string to calling functionunsigned char *PostFix(unsigned char *Expr){static struct Char_Stack *opStk = NULL;unsigned char *Post = NULL;unsigned char index = 0;unsigned char Temp;Post = (unsigned char *)malloc(strlen((char *)Expr) + 2);while(*Expr){if(isalpha(*Expr))Post[index++] = *Expr;if(*Expr == '(')Push_Char(&opStk, *Expr);if(*Expr == ')'){if(!IsEmpty(&opStk)){while(!IsEmpty(&opStk)){Temp = Pop_Chr(&opStk);if(Temp == '(')break;Post[index++] = Temp;}}}if(IsOperator(*Expr)){if(!IsEmpty(&opStk)){Temp = Pop_Chr(&opStk);if(IsOperator(Temp)){if(Value(Temp) < Value(*Expr)){Push_Char(&opStk, Temp);Push_Char(&opStk, *Expr);}else{Post[index++] = Temp;Push_Char(&opStk, *Expr);}}else{Push_Char(&opStk, Temp);Push_Char(&opStk, *Expr);}}elsePush_Char(&opStk, *Expr);}Expr++;}while(!IsEmpty(&opStk))Post[index++] = Pop_Chr(&opStk);Post[index] = NULL;return Post;}// Converts user supplied character string into token based alphabetical string for easement of calculationsunsigned char *Convert(unsigned char *Expr){unsigned char *nExpr = NULL;unsigned char Alpha = 'A';unsigned char *Temp = (unsigned char *)calloc(25, 1);unsigned char index = 0;unsigned char tindex = 0;unsigned char hasPoint = FALSE;nExpr = (unsigned char *)malloc(strlen((char *)Expr) + 2);while(*Expr){if(isdigit(*Expr) || *Expr == '.'){if(tindex == 0)nExpr[index++] = Alpha++;if(*Expr == '.')hasPoint = TRUE;Temp[tindex++] = *Expr;}elseif(*Expr == ')' || *Expr == '(')nExpr[index++] = *Expr;elseif(IsOperator(*Expr)){nExpr[index++] = *Expr;if(hasPoint == FALSE){Temp[tindex++] = '.';Temp[tindex] = '0';}AddQ(strtod((char *)Temp, (char **)NULL));memset(Temp, 0, sizeof(Temp));tindex = 0;hasPoint = FALSE;}Expr++;}if(hasPoint == FALSE){Temp[tindex++] = '.';Temp[tindex] = '0';}AddQ(strtod((char*)Temp, (char**)NULL));nExpr[index] = NULL;free(Temp);return nExpr;}// Calculates answer to any given expression and returns the final answerfloat Calculate(unsigned char *Expr){static struct Float_Stack *opStk = NULL;float Answer = 0;float Operator1, Operator2;// Convert the expression into alphabetical string for easement in evaluationExpr = Convert(Expr);// Convert the expression string into postfix notationExpr = PostFix(Expr);// Perform calculationswhile(*Expr){if(isalpha(*Expr))Push_Dbl(&opStk, DeleteQ());elseif(IsOperator(*Expr)){Operator2 = Pop_Dbl(&opStk);Operator1 = Pop_Dbl(&opStk);Answer = Binary_Operate(Operator1, Operator2, *Expr);Push_Dbl(&opStk, Answer);}Expr++;}// Final answerAnswer = Pop_Dbl(&opStk);return Answer;}int main(){static unsigned char Expr[255];float Ans;Init_LCD();LCD_Backlight(ON);while(1){// Read user input from serial terminal until '=' sign does not appearRxNchar(Expr, sizeof(Expr) - 1, '=');// Switch OFF the LCD backlight and evaluate expression for final answerLCD_Backlight(OFF);Ans = Calculate((unsigned char *)Expr);// Write final answer to the serial terminal as well as on LCD screenClear_LCD();Write_USART("%s%.4fn", Expr, Ans);Write_LCD(1, 1, "%16sn%16.4f", Expr, Ans);// Clear the input string for next operation and turn ON the LCD backlightmemset(Expr, 0, sizeof(Expr));LCD_Backlight(ON);}}###
Circuit Diagrams
Filed Under: Electronic Projects
Questions related to this article?
👉Ask and discuss on EDAboard.com and Electro-Tech-Online.com forums.
Tell Us What You Think!!
You must be logged in to post a comment.