You are on page 1of 27

DEDAN KIMATHI UNIVERSITY

OF TECHNOLOGY

MECHATRONIC SYSTEMS PROGRAMMING


II

UNIT CODE: EMT 3202


Project Report
TITLE: REMOTE MOTOR CONTROL

LECTURER: Mr. Gichane

GROUP MEMBERS

1. Caren Wakesho E022-01-1362/2021


2. Rejoice Wanjiku E022-01-1344/2021
3. Alex Kurgat E022-01-1358/2021
4. Barnabas Kiprotich E022-01-1359/2021
5. Brian Kiprono E022-01-1385/2021
6. Norman Muturi E022-01-2465/2021
7. Michael Machohi E022-01-1346/2021
8. Zebby Akach E022-01-1161/2021
9. Jesse Jowi E022-01-1407/2021
10. James Gathiriwa E022-01-1345/2021
Date Of Submission: 24/11/2023
OBJECTIVE
Precise motor control is a required feature in most, if not all embedded mechatronic systems.
It is critical for aerospace, aviation, robotics and industrial applications. In this project you are
required to create a Proteus simulation and write a control program for an encoded DC motor
whose speed and position is controlled by an Atmega32 MCU2. This control MCU2 will
receive speed and position information from a master Atmega32 MCU1. The simulation will
require the following items:
 Atmega32 - MCU1
 Atmega32 - MCU2
 16 button keypad interfaced using MM74C922
 (2)16x2 LCD
 Encoded DC Motor

The operations of the motor control system follow the sequence below:
1. MCU1 and MCU2 are connected through I2C communication. MCU1 acts as the master
device and sends mode commands, required motor speed and required motor position.
2. MCU1 will have a keypad and an LCD with the user can interact with. A user will be able
to select the mode using the mode_select button (the ‘+’ key on the keypad). The system will
have 2 modes
 Mode 1 – Speed control (M-Speed)
 Mode 2 – Position control (M-Pos)

Once M-Speed has been selected, the user can then input the required speed in RPM. This
should be displayed on the LCD. Once complete by pressing the Enter button (the ‘ON/C’
key on the keypad). The mode and speed setting is sent to MCU2 via I2C.
LCD Display Sample:
 Mode: M-Speed
 Speed: _ _ _ RPM

Similarly once M-Pos has been selected the user can then input the number of rotations for
the motor to turn. This should be displayed on the LCD. Once complete by pressing the Enter
button (the ‘ON/C’ key on the keypad). The mode and speed setting is sent to MCU2 via I2C.
LCD Display Sample:
 Mode: M-Pos
 Pos: _ _ _ REV

3. MCU2 will ensure the motor moves according to the setting required. It should display the
mode, the setting and the current value for speed or position of the motor.

LCD Display Sample:


 M-Speed->_ _ _ RPM
 Cur: _ _ _ RPM
LCD Display Sample:
 M-Pos->_ _ _ REV
 Cur: _ _ _ REV
4. MCU1 will have an emergency button (the ‘X’ key on the keypad). If it is pressed at any
time within the operations, MCU2 should stop and display an emergency stop alert on its
LCD screen.

LCD Display Sample:


 Emergency!
 Stop!
This state can only be undone by resetting MCU2 using the micro-controller reset button.
INTRODUCTION
In the engineering world, especially in robotics and mechatronics, precise motor
control is one of the cornerstones of system designs. Its applications span various
applications such as aerospace, aviation, robotics and industrial applications. It is in
such light that this project requires the design and implementation of an embedded
motor control system. This is to be accomplished using the Atmega 32
microcontroller. The microcontroller serve as the CPU of the while system. As per the
requirements the project is to allow for user input to select the mode and give
instruction for the precise control of the motor.
The system should incorporate two micro controllers MCU1 and MCU2. the MCU1 is
to serve as the primary controller to manage user input. Therefore the microcontroller
must be equipped with and LCD display and a Key pad. The MCU2 serves as the
secondary controller tasked with the control of the motor and display of current motor
status. The two micro controllers must therefore have the ability to communicate thus
the I2C protocol is to be applied with MCU1 as the master and MCU2 as the slave.
The master is responsible for user interaction, mode selection and issuing motor
control commands. The slave is tasked with executing motor movements based on
commands received from MCU1 and also display the current motor status.
For the purpose of mode selection, speed and position setting, a 4X4 keypad should
be used. The keypad as stated above should be connected to the master. Furthermore,
for display of the prompts a 16X2 LCD display this is in addition to mode status and
real time feedback on motor speed or position. Both the master and slave should be
fitted with their own display.
For precise motor control we will use and encoded DC motor. To achieve this a motor
driver will be applied and this the L293D driver will be used.

The sequence of operations are as follows,


 MCU1 and MCU2 communicate seamlessly through I2C communication, with
MCU1 serving as the master.
 The keypad set up at the master serves user interaction. The two available modes
are set and the ‘+’ key is used to select the mode to either M-Speed or M-Pos.
 Upon user confirmation the ‘C’ key is used as the enter to sends the mode and
corresponding settings to MCU2 through I2C.
 The MCU2 interprets the received commands and sends relevant instructions to
the motor and obtains real time information from the motor and displays it in the
LCD display.
 The emergency stop ‘X’ key should also be incorporated. When pressed MCU2
executes and emergency stop, displaying an alert on its LCD. It can only be
undone by resetting MCU2.
CIRCUIT IMPLIMENTATION
As instructed the circuit will use 2 Atmega 32, 2 LCD’s, a keypad and a Motor driver.
This is a brief descriptions of each component:
1. Atmega32 Microcontroller

The Atmega32 is an 8-bit microcontroller form AVR used in embedded systems. Is is


made up of programmable flash memory, RAM and various peripherals. It has input
and output pins, and the peripherals include ports for UART, SPI and I2C. it also has
provision for Analog to Digital converter(ADC).

Figure 1 Atmega32 Microcontroller

2. 16X2 LCD

LCD stands for Liquid Crystal Display. The 16X2 LCD is an Alphanumeric display.
It consists of a segmented screen of 16 columns and 2 rows, which allows for display
of up to 32 characters to transmit information. They are easy to interface and consume
little power hence are widely used in such systems.

Figure 2 16X2 LCD

LCD 16x2 is a 16 pin devices which has 8 data pins (D0-D7) and 3 control pins (RS,
RW, EN). The remaining 5 pins are for power supply and backlight for the LCD. The
3 control pins help us configure the LCD in command mode or data mode. They also
help to configure the LCD into read mode or write mode. LCD 16x2 can be used in 4-
bit mode or 8-bit mode depending on requirement of application. In order to use it we
need to send certain commands to the LCD in command mode and once the LCD is
configured according to our need, we can send the required data in read mode.
We shall use the LCD in 4 bit mode which is easier to interface to the Atmega32
3. 4X4 Keypad

This is an Input device that consists of a matrix of 16 keys arranged in a grid of 4


columns and 4 rows. Each key has a unique combination of Rows and Columns which
when pressed allows the user to input data. They are also simple to interface to the
microcontroller this can either be done directly or by use of a driver which reduces the
number of pins needed to set up the keypad
.

Figure 3 4X4 Keypad

4. L293D driver and encoded Motor


An encoded DC Motor is a type of DC motor equipped with and encoder fro
measuring its speed and position. The encoder provides Feedback to the control
system, allowing precise control of the motor’s rotational speed and the ability to
determine its angular velocity.
The L293D driver is a dual H Bridge motor driver integrated circuits allowing it to
control the direction and speed of two DC motors
Bellow are the images of
The Master and Slave Circuits
Next we look at the Master and slave circuits
 MCU1-Master Circuit

 MCU2 -Slave Circuit


The above are the circuit configurations of the MCU1 and MCU2 together with the
LCDs Keypad and Motor
THE FULL CIRCUIT
CODE IMPLIMENTATION
After completing the circuit we now get to the code the code is what runs the whole
circuit. Since the remote motor control project uses two Atmega32 micro controllers
this means we will need to have two separate codes for the MCU1 and MCU2, and
the link between the two will be through the I2C communication protocol.
The code is modulated to contain different function files wit the different functions to
improve efficiency and to eliminate redundancy. Apart from the LCD codes the rest
do the codes are different for the Master and Slave.
Below are the codes for the different functions:

a) LCD set-up functions

/*Typical Header file inclusion and CPU frequency definition*/


#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>

/*LCD command write function*/


#define LCD_Port PORTC
#define LCD_Cmd_Dir DDRC
#define RS PC2
#define EN PC3

//*LCD command write function*/


void LCD_Cmd(unsigned char cmd){
/*Sending the first nibble of data (Higher 4 bits)*/
LCD_Port = (LCD_Port & 0x0F) | (cmd & 0xF0);/* Sending upper nibble */
LCD_Port &= ~ (1<<RS); /* RS=0, command reg. */
LCD_Port |= (1<<EN); /* Enable pulse ON */
_delay_us(1);
LCD_Port &= ~ (1<<EN); /* Enable pulse OFF */
_delay_us(200);
/*Sending the second nibble of data (Lower 4 bits)*/
LCD_Port = (LCD_Port & 0x0F) | (cmd << 4);/* Sending lower nibble */
LCD_Port |= (1<<EN); /* Enable pulse ON */
_delay_us(1);
LCD_Port &= ~ (1<<EN); /* Enable pulse OFF */
_delay_ms(2);
}
/*LCD data write function */
void LCD_Char (unsigned char char_data){
/*Sending the first nibble of data (Higher 4 bits)*/
LCD_Port = (LCD_Port & 0x0F) | (char_data & 0xF0);/* Sending upper
nibble */
LCD_Port |= (1<<RS); /* RS=1, data reg. */
LCD_Port |= (1<<EN); /* Enable pulse ON */
_delay_us(1);
LCD_Port &= ~ (1<<EN); /* Enable pulse OFF */
_delay_us(200);
/*Sending the second nibble of data (Lower 4 bits)*/
LCD_Port = (LCD_Port & 0x0F) | (char_data << 4); /* Sending lower nibble
*/
LCD_Port |= (1<<EN); /* Enable pulse ON */
_delay_us(1);
LCD_Port &= ~ (1<<EN); /* Enable pulse OFF */
_delay_ms(2);
}
/*LCD Initialize function */
void LCD_Init (void){
LCD_Cmd_Dir = 0xFF; /* Make LCD command port direction as output pins*/
_delay_ms(20); /* LCD Power ON delay always > 15ms */
LCD_Cmd(0x02); /* Return display to its home position */
LCD_Cmd(0x28); /* 2 line 4bit mode */
LCD_Cmd(0x0C); /* Display ON Cursor OFF */
LCD_Cmd(0x06); /* Auto Increment cursor */
LCD_Cmd(0x01); /* Clear display */
}
/*Clear LCD Function*/
void LCD_Clear(void){
LCD_Cmd(0x01); /* clear display */
LCD_Cmd(0x02); /* Return display to its home position */
}
/*Send string to LCD function */
void LCD_String (char *str){
int i;
/* Send each char of string till the NULL */
for(i=0;str[i]!=0;i++){
LCD_Char(str[i]);
}
}
/*Send string to LCD with x_y position */
void LCD_String_xy (char row, char pos, char *str){
if (row == 0 && pos<16){
LCD_Cmd((pos & 0x0F)|0x80);/* Command of first row and required
position<16 */
}
else if (row == 1 && pos<16){
LCD_Cmd((pos & 0x0F)|0xC0);/* Command of second row and required
position<16 */
}
LCD_String(str); /* Call LCD string function */
}

b) Keypad Set-up

#include <string.h>
#define KEY_PIN PIND // Keypad input pin

/* Keypad array holding the keys in a grid arrangement*/


unsigned char keypad[4][4] = { {'7','8','9','/'},
{'4','5','6','*'},
{'1','2','3','-'},
{'C','0','=','+'}};
/* Function that checks the key that has been pressed on the keypad */
char check_Keypad()
{
unsigned char rowNum, colNum, input;
while(1)
{
DDRD = 0xF0; // Set the rows as outputs and the columns as inputs
PORTD = 0x0F; // Set the columns HIGH and the rows LOW.
/*With the columns HIGH and the rows LOW, thus the KEY_PIN ==
0x0F(0b0000 1111).
If a button is pressed, the value of the column pin will be
grounded and
KEY_PIN will change from 0x0F to some other number 0x0E i.e.
0x0E(0b0000 1110) i.e. a button in the 4th column was pressed.
*/
do
{
do
{
_delay_ms(20); // 20ms key de_bounce time
colNum = (KEY_PIN & 0x0F); // read status of column
}while(colNum == 0x0F); // check for any key press
_delay_ms (40); // 20 ms key de_bounce time
colNum = (KEY_PIN & 0x0F);
}while(colNum == 0x0F);
/*Once we have established which column the button is, we need to
know the row
To this we set the rows HIGH and ground them 1 pin at a time
while reading the result
on the column pins.
*/
PORTD = 0xEF; // Check for pressed key in 4th row
asm("NOP");
input = (KEY_PIN & 0x0F);
if(input != 0x0F)
{
rowNum = 3;
break;
}
PORTD = 0xDF; // Check for pressed key in 3rd row
asm("NOP");
input = (KEY_PIN & 0x0F);
if(input != 0x0F)
{
rowNum = 2;
break;
}
PORTD = 0xBF; // Check for pressed key in 2nd row
asm("NOP");
input = (KEY_PIN & 0x0F);
if(input != 0x0F)
{
rowNum = 1;
break;
}
PORTD = 0x7F; // Check for pressed key in 1st row
asm("NOP");
input = (KEY_PIN & 0x0F);
if(input != 0x0F)
{
rowNum = 0;
break;
}
}
/*After obtaining the column and row values, the final keypad key can be
output by cross
referencing
the rows and columns in our keypad character array
*/
if(colNum == 0x0E)
return(keypad[rowNum][3]);
else if(colNum == 0x0D)
return(keypad[rowNum][2]);
else if(colNum == 0x0B)
return(keypad[rowNum][1]);
else
return(keypad[rowNum][0]);
}

c) ADC Functions

/*Typical Header file inclusion and CPU frequency definition*/


#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
void ADC_Init() /* ADC Initialization function */
{
DDRA = 0x00; /* Make ADC port as input */
ADCSRA = 0x87; /* Enable ADC, with freq/128 */
ADMUX = 0x40; /* Vref: Avcc, ADC channel: 0 */
}
int ADC_Read(char channel) /* ADC Read function */
{
ADMUX = 0x40 | (channel & 0x07);/* set input channel to read */
ADCSRA |= (1<<ADSC); /* Start ADC conversion */
while (!(ADCSRA & (1<<ADIF))); /* Wait until end of conversion */
ADCSRA |= (1<<ADIF); /* Clear interrupt flag */
_delay_ms(1); /* Wait a little bit */
return ADCW; /* Return ADC word */
}

d) Timer Set-Up
/*Timer setup function*/
void timer_setup(){
TIMSK |= (1<<TOIE1); // Activate the timer overflow interrupt
TCCR1B = (1<<CS11)|(1<<CS10); // Set the timer prescalar to 64
TCNT1 = 3036; // Load the countdown value for 500ms
}

/*Timer overflow ISR*/


ISR(TIMER1_OVF_vect){
cur_count = count;
RPM = (cur_count-prev_count)*120/(96); // Calculate the RPMs
prev_count = cur_count;
TCNT1 = 3036;
}

e) Interrupt Set-up
/*Interrupt setup function*/
void interrupt_setup(){
DDRB = 0xFF; // Make PORTB as output Port
DDRB &= ~(1<<PB2); // Make INT2 pin as Input
DDRB |= (1<<PB3); // Make OC0 pin as Output
DDRD &= ~(1<<PD2); // Make INT0 pin as Input
DDRD &= ~(1<<PD3); // Make INT0 pin as Input
GICR = (1<<INT2)|(1<<INT1)|(1<<INT0); // Enable INT0, INT2
MCUCR = (1<<ISC00)|(1<<ISC10); // Trigger INT0 on Logic change trigger
MCUCSR = (1<<ISC2);// Trigger INT2 on Rising Edge triggered
sei(); // Enable Global Interrupt */
}
/* Interrupt ISR functions */
ISR(INT0_vect){
cur_encode = PIND & ((1<<PD2)|(1<<PD3));
cur_encode = (cur_encode>>2);
// From the encoder value chart, when Channel A changes logic states we
look
// at the value of the interrupt pins. If they are either 0b 11 or 0b
00,
// the motor is moving CW and we decrease the count
if(cur_encode == 0x03 || cur_encode == 0x00){
count-=1;
}
// If they are either 0b 10 or 0b 01, the motor is moving CCW and we
increase the count
else if(cur_encode == 0x02 || cur_encode == 0x01){
count+=1 ;
}
}

ISR(INT1_vect){
cur_encode = PIND & ((1<<PD2)|(1<<PD3)); // Obtain the reading from the
PIND2 and PIND3
cur_encode = (cur_encode>>2);
// From the encoder value chart, when Channel B changes logic states we
look
// at the value of the interrupt pins. If they are either 0b 01 or 0b
10,
// the motor is moving CW and we decrease the count
if(cur_encode == 0x01 || cur_encode == 0x02){
count-=1;
}
// If they are either 0b 11 or 0b 00, the motor is moving CCW and we
increase the count
else if(cur_encode == 0x03 || cur_encode == 0x00){
count+=1;
}
}

ISR(INT2_vect){
Direction = ~Direction; // Toggle Direction
_delay_ms(50); // Software de-bouncing control delay
}
f) PWM Set-up

MASTER MAIN CODE AND I2C FUNCTIONS


a. Master I2C Functions

#define F_CPU 8000000UL /* Define CPU clock Frequency 8MHz */


#include <avr/io.h> /* Include AVR std. library file */
#include <util/delay.h> /* Include inbuilt defined Delay header file */
#include <stdio.h> /* Include standard I/O header file */
#include <stdlib.h> /* Include string header file */
#include <avr/interrupt.h>
#define BITRATE

void I2C_Init() /* I2C initialize function */


{
TWBR = BITRATE(TWSR = 0x00); /* Get bit rate register value by formula
*/
}
uint8_t I2C_Start(char write_address) /* I2C start function */
{
uint8_t status;
/* Declare variable */
TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);/* Enable TWI, generate start
condition and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(start condition) */
status = TWSR & 0xF8; /* Read TWI status register with masking lower
three bits */
if (status != 0x08) /* Check whether start condition transmitted
successfully or not? */
return 0; /* If not then return 0 to indicate start condition fail */
TWDR = write_address; /* If yes then write SLA+W in TWI data register */
TWCR = (1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(Write operation) */
status = TWSR & 0xF8; /* Read TWI status register with masking lower
three bits */
if (status == 0x18) /* Check whether SLA+W transmitted & ack received or
not? */
return 1; /* If yes then return 1 to indicate ack received; ready to
accept data*/
if (status == 0x20) /* Check whether SLA+W transmitted & nack received
or not? */
return 2; /* If yes then return 2 to indicate nack received i.e. device
is busy */
else
return 3; /* Else return 3 to indicate SLA+W failed */
}
uint8_t I2C_Repeated_Start(char read_address) /* I2C repeated start function */
{
uint8_t status; /* Declare variable */
TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);/* Enable TWI, generate start
condition and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(start condition) */
status = TWSR & 0xF8; /* Read TWI status register with masking lower
three bits */
if (status != 0x10) /* Check whether repeated start condition
transmitted successfully*/
return 0; /* If no then return 0 to indicate repeated start condition
fail */
TWDR = read_address; /* If yes then write SLA+R in TWI data register */
TWCR = (1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(Write operation) */
status = TWSR & 0xF8; /* Read TWI status register with masking lower
three bits */
if (status == 0x40) /* Check weather SLA+R transmitted & ack received or
not? */
return 1; /* If yes then return 1 to indicate ack received */
if (status == 0x20) /* Check weather SLA+R transmitted & nack received
or not? */
return 2; /* If yes then return 2 to indicate nack received i.e. device
is busy */
else
return 3; /* Else return 3 to indicate SLA+W failed */
}
void I2C_Stop() /* I2C stop function */
{
TWCR=(1<<TWSTO)|(1<<TWINT)|(1<<TWEN); /* Enable TWI, generate stop
condition and clear interrupt flag */
while(TWCR & (1<<TWSTO)); /* Wait until stop condition execution */
}
void I2C_Start_Wait(char write_address) /* I2C start wait function */
{
uint8_t status; /* Declare variable */
while (1)
{
TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);/* Enable TWI, generate
start condition and clear interrupt
flag */
while (!(TWCR & (1<<TWINT)));/* Wait until TWI finish its current
job (start condition) */
status = TWSR & 0xF8; /* Read TWI status register with masking
lower three bits */
if (status != 0x08) /* Check whether start condition transmitted
successfully or not? */
continue; /* If no then continue with start loop again */
TWDR = write_address; /* If yes then write SLA+W in TWI data
register */
TWCR = (1<<TWEN)|(1<<TWINT);/* Enable TWI and clear interrupt
flag */
while (!(TWCR & (1<<TWINT)));/* Wait until TWI finish its current
job (Write operation) */
status = TWSR & 0xF8; /* Read TWI status register with masking
lower three bits */
if (status != 0x18 ) /* Check whether SLA+W transmitted & ack
received or not? */
{
I2C_Stop(); /* If not then generate stop condition */
continue; /* continue with start loop again */
}
break; /* If yes then break loop */
}
}
uint8_t I2C_Write(char data) /* I2C write function */
{
uint8_t status; /* Declare variable */
TWDR = data; /* Copy data in TWI data register */
TWCR = (1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(Write operation) */
status = TWSR & 0xF8; /* Read TWI status register with masking lower
three bits */
if (status == 0x28) /* Check weather data transmitted & ack received or
not? */
return 0; /* If yes then return 0 to indicate ack received */
if (status == 0x30) /* Check weather data transmitted & nack received or
not? */
return 1; /* If yes then return 1 to indicate nack received */
else
return 2; /* Else return 2 to indicate data transmission failed */
}
char I2C_Read_Ack() /* I2C read ack function */
{
TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWEA); /* Enable TWI, generation of ack
and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(read operation) */
return TWDR; /* Return received data */
}
char I2C_Read_Nack() /* I2C read nack function */
{
TWCR=(1<<TWEN)|(1<<TWINT); /* Enable TWI and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(read operation) */
return TWDR; /* Return received data */
}
b. Master Main Code

#define F_CPU 8000000UL /* Define CPU clock Frequency 8MHz */


#include <avr/io.h> /* Include AVR std. library file */
#include <util/delay.h> /* Include inbuilt defined Delay header file */
#include <stdio.h> /* Include standard I/O header file */
#include <stdlib.h> /* Include string header file */
#include <avr/interrupt.h>
#include <string.h>
#include "I2C_Master_Functions.h"
#include "I2C_LCD_Initialization.h"
#include "Keypad_Initialization.h"

#define Slave_Write_Address 0x20


#define Slave_Read_Address 0x21
#define count 10
#define MAX_DISPLAY_LENGTH 3 // Maximum characters to display on the LCD
#define MAX_INPUT_LENGTH 3 // Maximum number of digits for input
char key ;
volatile char key_pressed = '\0';
uint8_t mode = 0; // Variable to track the mode
int cursorPosition = 4;

int keycount = -1;


uint8_t sensor_select = 0;
uint16_t result;
/*A function to send a serial number the slave and read the slave's result*/
void I2C_Read_Sensor(uint8_t data){
char buffer[10];
I2C_Start_Wait(Slave_Write_Address); // Start I2C with SLA+W
_delay_ms(50); // Important time delay for communication
I2C_Write(data); // Send Incrementing count
_delay_ms(50); // Important time delay for communication
I2C_Repeated_Start(Slave_Read_Address); // Repeated Start I2C
communication with SLA+R
_delay_ms(5); // Important time delay for communication
sprintf(buffer, "%d", I2C_Read_Ack()); // Read and send Acknowledge of
data
_delay_ms(50); // Important time delay for communication
result = I2C_Read_Nack();
sprintf(buffer, "%d", result); // Read and Not Acknowledge to data
LCD_String_xy(1, 11, buffer);
_delay_ms(50); // Important time delay for communication
I2C_Stop(); // Stop I2C
}

/*ISR function: run whenever there is a new key press from the MMC74C922*/
ISR(INT0_vect){
// Read the value from the keypad connected pins
char value = PINB & (0x0F);
unsigned char keycheck = check_Keypad(value);
// If the key pressed is C, send a message to MCU2
if (keycheck == '+'){
mode = ~mode; // Toggle mode
LCD_Clear();
_delay_ms(50); // Software de-bouncing control delay
}else if (keycheck == 'C'){
send_cmd = ~send_cmd;
keycount = 0;
}else if (keycheck == '*'){
PORTA |= (1<<PA7);
_delay_ms(100);
PORTA &= ~(1<<PA7);
}else{
if(keycount < 0){
keycount++;
}else if((keycount>= 0) && (keycount < snd_len)){
send_msg[keycount] = keycheck;
keycount++;
if(keycount == snd_len) keycount = 0;
}
}
}
/*Function to select the mode to display*/
//Passed value from main function is used as switch value
void mode_sel(int case_val){
int buffer;
int count;
switch (case_val)
{
//Position display and sending of values
case 0:
//Display setup
LCD_String_xy(0,0, "Mode:M-Pos");
LCD_String_xy(1,0,"Pos:");
LCD_String(send_msg); //Displays the pressed characters
LCD_String_xy(1,11,"REV");
//Checks if the send_cmd is toggled in order to send
if (send_cmd)
{
buffer = atoi(send_msg); //Conversion of message from
string to integer
//increase the converted value by one
//Send both the converted value and increased value in
order to recognize the mode sent to slave
count = buffer+1;
//For loop for sending both values
for (get = 0;get<2;get++)
{
if (get == 0)
{
SPI_Write(buffer);
} if (get == 0)
{
SPI_Write(count);
}
}
send_cmd = FALSE; //Return send command to initial state
clear_send(send_msg); //clear message buffer
}
break;
case 1:
//Display setup
LCD_String_xy(0,0, "Mode:M-Speed");
LCD_String_xy(1,0,"Speed:");
LCD_String(send_msg); //Displays the pressed characters
LCD_String_xy(1,11,"RPM");
if (send_cmd)
{
buffer = atoi(send_msg); //String to integer conversion
//Send the converted value twice in order to recognize the
mode sent to slave
for (get = 0;get<2;get++)
{
SPI_Write(buffer);
}
send_cmd = FALSE; //Return send command to initial state
clear_send(send_msg); //clear message buffer
}
break;
}
}

int main(void)
{
LCD_Init();
I2C_Slave_Init(Slave_Address);
keypad_Init();
/*Emergency stop pin direction*/
DDRA = 1<<PA7;
/* Replace with your application code */
while (1)
{

// Checks for the state of the mode status whether 0 or 1


//Passes the value 0 or 1 to the mode selection function
if (mode !=0) {
mode_sel(0);
}
else{
mode_sel(1);
}
}
}

SLAVE MAINCODE AND I2C FUNCTIONS


a. Slave I2C Functions

#define F_CPU 8000000UL /* Define CPU clock Frequency 8MHz */


#include <avr/io.h> /* Include AVR std. library file */
#include <util/delay.h> /* Include inbuilt defined Delay header file */
#include <stdio.h> /* Include standard I/O header file */
#include <stdlib.h> /* Include string header file */
#include <avr/interrupt.h>

void I2C_Slave_Init(uint8_t slave_address)


{
TWAR = slave_address; /* Assign address in TWI address register */
TWCR = (1<<TWEN) | (1<<TWEA) | (1<<TWINT);/* Enable TWI, Enable ack
generation, clear TWI interrupt */
}
int8_t I2C_Slave_Listen()
{
while(1){
uint8_t status; /* Declare variable */
while (!(TWCR & (1<<TWINT)));/* Wait to be addressed */
status = TWSR & 0xF8; /* Read TWI status register with masking
lower three bits */
if (status == 0x60 || status == 0x68) /* Check whether own SLA+W
received & ack returned (TWEA = 1) */
return 0; /* If yes then return 0 to indicate ack returned */
if (status == 0xA8 || status == 0xB0) /* Check whether own SLA+R
received & ack returned (TWEA = 1) */
return 1; /* If yes then return 1 to indicate ack returned */
if (status == 0x70 || status == 0x78) /* Check whether general
call received & ack returned (TWEA = 1) */
return 2; /* If yes then return 2 to indicate ack returned */
else
continue; /* Else continue */
}
}
int8_t I2C_Slave_Transmit(char data)
{
uint8_t status;
TWDR = data; /* Write data to TWDR to be transmitted */
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);/* Enable TWI and clear interrupt
flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(Write operation) */
status = TWSR & 0xF8; /* Read TWI status register with masking lower
three bits */
if (status == 0xA0) /* Check whether STOP/REPEATED START received */
{
TWCR |= (1<<TWINT); /* If yes then clear interrupt flag & return
-1 */
return -1;
}
if (status == 0xB8) /* Check whether data transmitted & ack received */
return 0; /* If yes then return 0 */
if (status == 0xC0) /* Check whether data transmitted & nack received */
{
TWCR |= (1<<TWINT); /* If yes then clear interrupt flag & return
-2 */
return -2;
}
if (status == 0xC8) /* If last data byte transmitted with ack received
TWEA = 0 */
return -3; /* If yes then return -3 */
else /* else return -4 */
return -4;
}
char I2C_Slave_Receive()
{
uint8_t status; /* Declare variable */
TWCR=(1<<TWEN)|(1<<TWEA)|(1<<TWINT); /* Enable TWI, generation of ack
and clear interrupt flag */
while (!(TWCR & (1<<TWINT))); /* Wait until TWI finish its current job
(read operation) */
status = TWSR & 0xF8; /* Read TWI status register with masking lower
three bits */
if (status == 0x80 || status == 0x90) /* Check whether data received &
ack returned (TWEA = 1) */
return TWDR; /* If yes then return received data */
if (status == 0x88 || status == 0x98) /* Check whether data received,
nack returned and switched to not
addressed slave mode */
return TWDR; /* If yes then return received data */
if (status == 0xA0) /* Check whether STOP/REPEATED START received */
{
TWCR |= (1<<TWINT); /* If yes then clear interrupt flag & return
0 */
return -1;
}
else
return -2; /* Else return 1 */
}
b. Slave Main code

#define F_CPU 8000000UL /* Define CPU clock Frequency e.g. here its 8MHz */
#include <avr/io.h> /* Include AVR std. library file */
#include <util/delay.h> /* Include inbuilt defined Delay header file */
#include <stdio.h> /* Include standard I/O header file */
#include <stdlib.h> /* Include string header file */
#include "I2C_LCD_Initialization.h" /* Include LCD header file */
#include "I2C_Slave_Functions.h" /* Include I2C slave header file */
#include "Keypad_Initialization.h"
#define Slave_Address 0x20

int8_t sensor_select = 0;

void ADC_Init(); /* ADC Initialization function */


int ADC_Read(char channel); /* ADC Read function */

void pwm_setup(){
/* GPIO setup */
DDRB |= (1<<PB3); // Make OC0 pin as Output
/* PWM setup */
TCCR0 = (1<<WGM00)|(1<<WGM01)|(1<<COM01)|(1<<CS02); // Set Fast PWM with
Fosc/256 Timer0 clock
}

/*Motor speed setup function*/


void speed_ctrl(char motor_speed){
char buffer1[4];
int std_speed;
//standard speed setup of maximum 70RPM = 255
std_speed = motor_speed*3.65; //Convert input value to a fraction
of 255
PORTB = 0x01;
//If condition to ensure the motor RPM and OCR0 is above 0 and
below maximum
if (std_speed > 255){
std_speed = 255;
motor_speed = 70;
} else if (std_speed<10){
std_speed = 10;
motor_speed = 10;
}else{
std_speed = std_speed;
motor_speed = motor_speed;
}
OCR0 = std_speed; //map received values to OCR0

//display the RPM value


sprintf(buffer,"%d",motor_speed);
LCD_String_xy(0,10,buffer);
//Track the RPM and display
start = ~start;
//Loop to monitor speed status or motor
while (start){
if (stop != 0) break; //Toggle status change causes loop
to exit
LCD_Cmd(0xC6);
dtostrf(RPM,2,2,buffer1); //convert double to string
LCD_String(buffer1);
LCD_String(" ");
_delay_ms(500);
}
}

/*Setup function for number of revolutions*/


void pos_ctrl(char rev){
char buffer1[10];
int n_Rev;
OCR0 = 218; // OCRO value for 60 revolutions per second i.e. 1
revolution per second
//minimum value set is 5 and maximum is 40 revolutions
if (rev<5){
n_Rev = 5;
}else if (rev > 40){
n_Rev = 40;
}else{
n_Rev = rev;
}
//display the number of revolutions to be done
sprintf(buffer,"%d",n_Rev); //integer to string conversion
LCD_String_xy(0,7,buffer);
//while loop condition for 1 second per loop
PORTB = 0x01;
while(c != n_Rev){
if (stop != 0) break; //Toggle status change causes loop
to exit
c++;
sprintf(buffer1, "%d",c); //convert nth_loop to string
LCD_Cmd(0xc6); //LCD Position setup
LCD_String(buffer1); //display the nth revolution
_delay_ms(1000); // Delay loop for 1 second
}
PORTB = 0x03; // Initiate motor brakes after LOOP is done
}

/*Select loop Mode i.e. speed/position*/


void Mode_Set(char comp1, char comp2){
char mode_val[4];
int mode_sel;
mode_sel = comp2%comp1; //Modulus returns either 0 or 1 to
determine mode
switch(mode_sel){ //returned value passed to switch case
case 0: // Switch case for speed control selection
//LCD setup function for speed control
LCD_String_xy(0,0,"M-SPEED->");
sprintf(mode_val, "%d",comp1);
LCD_String_xy(0,13,"RPM");
//value passed to control setup function
LCD_String_xy(1,0,"Cur:");
LCD_String_xy(1,12,"RPM");
speed_ctrl(comp1);
break;
case 1: //Switch case for position control setup
//LCD setup function for position control
LCD_String_xy(0,0,"M-POS->");
sprintf(mode_val, "%d",comp1);
LCD_String_xy(0,10,"REV");
//value passed to control setup function
LCD_String_xy(1,0,"Cur:");
LCD_String_xy(1,10,"REV");
pos_ctrl(comp1);
break;
}
}

/*Setup function for received data*/


void received(){
char message1, message2;
int k = 0;
//Loop to read the data sent in form of characters and assigns to
new char declarations
do{
character = SPI_Receive();
if(k==0){
message1 = character;
}else{
message2 = character;
}
k++;
}while (k <2); // Loop stop condition
//Received characters passed to mode setup function
Mode_Set(message1,message2);
}

int main(void)
{
LCD_Init();
SPI_Init();
pwm_setup();
interrupt_setup();
timer_setup();
/* Replace with your application code */
while (1)
{
received();
}
Outputs
 Mode selection

The first mode was Speed selection and is shown bellow;

The second was position selection

 Real-time Output
The MCU2 was to give real time output as a comparison to the selected settings by
the user. This was for both the Position Mode and Speed mode as illustrated below

Image 1 Real-time Speed information from MCU2


Image 2 Real-Time Position Information from MCU2

 Emergency Stop
When the ‘X’ key was presses the emergency stop was supposed to be executed by
MCU2 stopping the motor immediately and displaying “EMERGENCY! STOP!”.
as shown Bellow;

Image 3 Emergency Stop function

 Motor Running
The motor runs as instructed and gives feedback

Image 4 Running Motor


CONCLUSSION
In conclusion, the designed mechatronic system successfully integrates two Atmega32
microcontroller through I2C communication to achieve precise control over an
encoded DC motor. The system incorporates a 16-button keypad interfaced using the
MM74C922, two 16x2 LCDs, and an encoded DC motor connected to an L293D
driver.

The primary features of the system include two operational modes: speed control (M-
Speed) and position control (M-Pos). The user interface, consisting of the keypad and
LCD on MCU1, allows users to select the desired mode, input parameters (speed or
position), and initiate the motor control sequence. The I2C communication facilitates
the transmission of mode commands, required motor speed, and position from MCU1
to MCU2.

Upon selecting the speed control mode, users can input the desired speed in RPM,
displayed on the LCD. Similarly, in position control mode, users input the number of
rotations for the motor. MCU2 receives these settings, ensuring the motor moves
accordingly and displays the mode, setting, and current motor value on its LCD.

The system incorporates a safety feature where MCU1 includes an emergency button
('X' key), which, when pressed, triggers an emergency stop on MCU2. The emergency
stop is indicated on MCU2's LCD, and this state can only be reversed by resetting
MCU2 using the microcontroller reset button.

The project demonstrates the effective use of embedded systems, I2C communication,
and mechatronics principles. The combination of user interface elements, motor
control, and safety features showcases the versatility and practicality of the designed
system. The successful implementation of this mechatronic system aligns with the
requirements of various applications, including aerospace, aviation, robotics, and
industrial automation, where precise motor control is paramount. The modular design
allows for further expansion and customization, making it a robust platform for
diverse mechatronic applications.

REFFERENCES
i. EMT 3202 Mechatronics System Programming 2 Notes 1 and Notes 2

ii. Che, A. (2008). PID Control System Implementation in Embedded System for
DC Motor Speed Control.

iii. d’Avella, A., Giese, M., Ivanenko, Y. P., Schack, T., & Flash, T. (2016).
Modularity in Motor Control: From Muscle Synergies to Cognitive Action
Representation. Frontiers Media SA.

iv. M
‌ ishra, B. K., Borah, S., & Hemant Kasturiwale. (2022). Computing and
Communications Engineering in Real-Time Application Development. CRC
Press.

You might also like