/*
* External BLDC Motor Controller with 2004 LCD Display
* Arduino Nano with Serial Control Interface
* For 18-Pole BLDC Motor with SPWM Drive
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// LCD Configuration (I2C 2004 LCD)
LiquidCrystal_I2C lcd(0x27, 20, 4); // Set the LCD address to 0x27 for 20x4 display
// Button Pins
const int BUTTON_UP = 2;
const int BUTTON_DOWN = 3;
const int BUTTON_SELECT = 4;
const int BUTTON_BACK = 5;
const int BUTTON_START = 6;
const int BUTTON_STOP = 7;
const int BUTTON_REVERSE = 8;
// LED Indicators
const int LED_RUN = 9;
const int LED_FAULT = 10;
const int LED_REVERSE = 11;
const int BUZZER = 12;
// Encoder (optional)
const int ENC_A = A0;
const int ENC_B = A1;
const int ENC_BUTTON = A2;
// Menu System Variables
int menuIndex = 0;
int subMenuIndex = 0;
bool inSubMenu = false;
unsigned long lastButtonPress = 0;
const unsigned long DEBOUNCE_DELAY = 200;
// Motor Control Variables
float targetSpeed = 0.0;
float currentSpeed = 0.0;
float setpointSpeed = 0.0;
bool motorRunning = false;
bool reverseDirection = false;
bool faultCondition = false;
// Display Update Timing
unsigned long lastDisplayUpdate = 0;
unsigned long lastSerialUpdate = 0;
const unsigned long DISPLAY_INTERVAL = 1000;
const unsigned long SERIAL_INTERVAL = 100;
// Data Storage
String serialBuffer = "";
String faultMessage = "No Fault";
float voltage = 0.0;
float currentU = 0.0;
float currentV = 0.0;
float temperature = 0.0;
int modulationIndex = 0;
// Menu Structure
const char* mainMenu[] = {
"Speed Control",
"Direction",
"System Status",
"Parameters",
"Fault History",
"System Config"
};
const char* directionMenu[] = {
"Forward",
"Reverse",
"Toggle"
};
// EEPROM Storage for parameters
struct SystemParams {
float maxSpeed;
float accelRate;
float decelRate;
int softStartTime;
int currentLimit;
} params = {1000.0, 100.0, 150.0, 3000, 4};
// ==================== SETUP ====================
void setup() {
// Initialize pins
initializePins();
// Initialize LCD
initializeLCD();
// Initialize Serial communication with motor controller
Serial.begin(115200);
// Load parameters from EEPROM
loadParameters();
// Display startup screen
showStartupScreen();
Serial.println("External Controller Ready");
Serial.println("COMMANDS: START, STOP, SET_SPD:, SET_REV:, GET_STATUS");
}
// ==================== MAIN LOOP ====================
void loop() {
// Read and process serial commands from motor controller
processSerialCommands();
// Handle button inputs
handleButtons();
// Update display at regular intervals
if (millis() - lastDisplayUpdate >= DISPLAY_INTERVAL) {
updateDisplay();
lastDisplayUpdate = millis();
}
// Send status requests to motor controller
if (millis() - lastSerialUpdate >= SERIAL_INTERVAL) {
sendStatusRequest();
lastSerialUpdate = millis();
}
// Handle encoder input (if used)
handleEncoder();
}
// ==================== INITIALIZATION FUNCTIONS ====================
void initializePins() {
// Button inputs with pull-up resistors
pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_DOWN, INPUT_PULLUP);
pinMode(BUTTON_SELECT, INPUT_PULLUP);
pinMode(BUTTON_BACK, INPUT_PULLUP);
pinMode(BUTTON_START, INPUT_PULLUP);
pinMode(BUTTON_STOP, INPUT_PULLUP);
pinMode(BUTTON_REVERSE, INPUT_PULLUP);
// LED outputs
pinMode(LED_RUN, OUTPUT);
pinMode(LED_FAULT, OUTPUT);
pinMode(LED_REVERSE, OUTPUT);
pinMode(BUZZER, OUTPUT);
// Encoder inputs
pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
pinMode(ENC_BUTTON, INPUT_PULLUP);
// Turn off all LEDs initially
digitalWrite(LED_RUN, LOW);
digitalWrite(LED_FAULT, LOW);
digitalWrite(LED_REVERSE, LOW);
digitalWrite(BUZZER, LOW);
}
void initializeLCD() {
lcd.init();
lcd.backlight();
lcd.clear();
// Create custom characters for better display
createCustomChars();
}
void createCustomChars() {
// Create custom characters for arrows, etc.
byte upArrow[8] = {
B00100,
B01110,
B10101,
B00100,
B00100,
B00100,
B00100,
B00100
};
byte downArrow[8] = {
B00100,
B00100,
B00100,
B00100,
B00100,
B10101,
B01110,
B00100
};
lcd.createChar(0, upArrow);
lcd.createChar(1, downArrow);
}
void showStartupScreen() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" BLDC Motor Controller");
lcd.setCursor(0, 1);
lcd.print(" 18-Pole 350V DC");
lcd.setCursor(0, 2);
lcd.print(" SPWM Drive System");
lcd.setCursor(0, 3);
lcd.print(" Initializing...");
delay(2000);
lcd.clear();
updateDisplay();
}
// ==================== SERIAL COMMUNICATION ====================
void processSerialCommands() {
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
parseSerialCommand(serialBuffer);
serialBuffer = "";
} else {
serialBuffer += c;
}
}
}
void parseSerialCommand(String command) {
command.trim();
if (command.startsWith("STATUS:")) {
parseStatusData(command);
}
else if (command.startsWith("FAULT:")) {
parseFaultData(command);
}
else if (command.startsWith("SPEED:")) {
parseSpeedData(command);
}
else if (command.startsWith("CURRENT:")) {
parseCurrentData(command);
}
else if (command.startsWith("VOLTAGE:")) {
parseVoltageData(command);
}
else if (command.startsWith("TEMP:")) {
parseTemperatureData(command);
}
else if (command == "START_OK") {
motorRunning = true;
updateLEDs();
beep(1);
}
else if (command == "STOP_OK") {
motorRunning = false;
updateLEDs();
beep(2);
}
else if (command == "REVERSE_OK") {
reverseDirection = !reverseDirection;
updateLEDs();
beep(1);
}
}
void parseStatusData(String data) {
// Format: STATUS:SPD123.5,RUN1,REV0,FAULT0,MOD85
int start = data.indexOf("SPD") + 3;
int end = data.indexOf(",", start);
if (start >= 3 && end > start) {
currentSpeed = data.substring(start, end).toFloat();
}
start = data.indexOf("RUN") + 3;
end = data.indexOf(",", start);
if (start >= 3 && end > start) {
motorRunning = data.substring(start, end).toInt();
}
start = data.indexOf("REV") + 3;
end = data.indexOf(",", start);
if (start >= 3 && end > start) {
reverseDirection = data.substring(start, end).toInt();
}
start = data.indexOf("FAULT") + 5;
end = data.indexOf(",", start);
if (start >= 5 && end > start) {
faultCondition = data.substring(start, end).toInt();
}
start = data.indexOf("MOD") + 3;
end = data.length();
if (start >= 3) {
modulationIndex = data.substring(start, end).toInt();
}
}
void parseFaultData(String data) {
// Format: FAULT:Overcurrent Protection
faultMessage = data.substring(6);
faultCondition = true;
updateLEDs();
triggerAlarm();
}
void parseSpeedData(String data) {
currentSpeed = data.substring(6).toFloat();
}
void parseCurrentData(String data) {
// Format: CURRENT:U3.45,V3.23
int startU = data.indexOf("U") + 1;
int endU = data.indexOf(",", startU);
int startV = data.indexOf("V") + 1;
if (startU >= 1 && endU > startU) {
currentU = data.substring(startU, endU).toFloat();
}
if (startV >= 1) {
currentV = data.substring(startV).toFloat();
}
}
void parseVoltageData(String data) {
voltage = data.substring(8).toFloat();
}
void parseTemperatureData(String data) {
temperature = data.substring(5).toFloat();
}
void sendStatusRequest() {
Serial.println("GET_STATUS");
}
void sendMotorCommand(String command) {
Serial.println(command);
}
// ==================== BUTTON HANDLING ====================
void handleButtons() {
unsigned long currentTime = millis();
if (currentTime - lastButtonPress < DEBOUNCE_DELAY) {
return;
}
if (!digitalRead(BUTTON_START)) {
startMotor();
lastButtonPress = currentTime;
}
else if (!digitalRead(BUTTON_STOP)) {
stopMotor();
lastButtonPress = currentTime;
}
else if (!digitalRead(BUTTON_REVERSE)) {
toggleDirection();
lastButtonPress = currentTime;
}
else if (!digitalRead(BUTTON_UP)) {
increaseSpeed();
lastButtonPress = currentTime;
}
else if (!digitalRead(BUTTON_DOWN)) {
decreaseSpeed();
lastButtonPress = currentTime;
}
else if (!digitalRead(BUTTON_SELECT)) {
selectButton();
lastButtonPress = currentTime;
}
else if (!digitalRead(BUTTON_BACK)) {
backButton();
lastButtonPress = currentTime;
}
}
void startMotor() {
if (!faultCondition) {
sendMotorCommand("START");
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" Starting Motor...");
} else {
showFaultMessage();
}
}
void stopMotor() {
sendMotorCommand("STOP");
lcd.clear();
lcd.setCursor(0, 1);
lcd.print(" Stopping Motor...");
}
void toggleDirection() {
if (!motorRunning) {
sendMotorCommand("REVERSE");
} else {
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Stop motor first!");
delay(2000);
updateDisplay();
}
}
void increaseSpeed() {
if (inSubMenu) {
handleMenuNavigation(1);
} else {
setpointSpeed += 10.0;
if (setpointSpeed > params.maxSpeed) {
setpointSpeed = params.maxSpeed;
}
sendMotorCommand("SET_SPD:" + String(setpointSpeed));
updateDisplay();
}
}
void decreaseSpeed() {
if (inSubMenu) {
handleMenuNavigation(-1);
} else {
setpointSpeed -= 10.0;
if (setpointSpeed < 0) {
setpointSpeed = 0;
}
sendMotorCommand("SET_SPD:" + String(setpointSpeed));
updateDisplay();
}
}
void selectButton() {
if (!inSubMenu) {
enterSubMenu();
} else {
executeMenuAction();
}
}
void backButton() {
if (inSubMenu) {
exitSubMenu();
}
}
// ==================== ENCODER HANDLING ====================
void handleEncoder() {
static int lastEncA = HIGH;
static int lastEncB = HIGH;
int encA = digitalRead(ENC_A);
int encB = digitalRead(ENC_B);
if (lastEncA == HIGH && encA == LOW) {
if (encB == HIGH) {
increaseSpeed();
} else {
decreaseSpeed();
}
}
lastEncA = encA;
lastEncB = encB;
if (!digitalRead(ENC_BUTTON) && millis() - lastButtonPress > DEBOUNCE_DELAY) {
selectButton();
lastButtonPress = millis();
}
}
// ==================== MENU SYSTEM ====================
void handleMenuNavigation(int direction) {
if (inSubMenu) {
subMenuIndex += direction;
// Handle submenu bounds based on current menu
} else {
menuIndex += direction;
if (menuIndex < 0) menuIndex = 5;
if (menuIndex > 5) menuIndex = 0;
}
updateDisplay();
}
void enterSubMenu() {
inSubMenu = true;
subMenuIndex = 0;
updateDisplay();
}
void exitSubMenu() {
inSubMenu = false;
updateDisplay();
}
void executeMenuAction() {
switch(menuIndex) {
case 0: // Speed Control
// Already handled by up/down buttons
break;
case 1: // Direction
handleDirectionMenu();
break;
case 2: // System Status
// Just display information
break;
case 3: // Parameters
handleParameterMenu();
break;
case 4: // Fault History
showFaultHistory();
break;
case 5: // System Config
handleSystemConfig();
break;
}
}
void handleDirectionMenu() {
switch(subMenuIndex) {
case 0: // Forward
if (reverseDirection) {
toggleDirection();
}
break;
case 1: // Reverse
if (!reverseDirection) {
toggleDirection();
}
break;
case 2: // Toggle
toggleDirection();
break;
}
exitSubMenu();
}
void handleParameterMenu() {
// Parameter adjustment implementation
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Parameter Adjust");
lcd.setCursor(0, 1);
lcd.print("Use UP/DOWN buttons");
delay(2000);
exitSubMenu();
}
void showFaultHistory() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Fault History:");
lcd.setCursor(0, 1);
lcd.print(faultMessage);
delay(3000);
exitSubMenu();
}
void handleSystemConfig() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("System Config");
lcd.setCursor(0, 1);
lcd.print("Save to EEPROM?");
// Implementation for saving configuration
delay(2000);
exitSubMenu();
}
// ==================== DISPLAY FUNCTIONS ====================
void updateDisplay() {
lcd.clear();
if (faultCondition) {
showFaultScreen();
return;
}
if (inSubMenu) {
showSubMenu();
} else {
showMainScreen();
}
}
void showMainScreen() {
// Line 1: Status and Speed
lcd.setCursor(0, 0);
lcd.print(motorRunning ? "RUN " : "STOP");
lcd.print(reverseDirection ? " REV " : " FWD ");
lcd.print("SPD:");
lcd.print(currentSpeed, 0);
lcd.print(" RPM");
// Line 2: Setpoint and Modulation
lcd.setCursor(0, 1);
lcd.print("Set:");
lcd.print(setpointSpeed, 0);
lcd.print(" RPM Mod:");
lcd.print(modulationIndex);
lcd.print("%");
// Line 3: Current and Voltage
lcd.setCursor(0, 2);
lcd.print("I:");
lcd.print(currentU, 1);
lcd.print("A V:");
lcd.print(voltage, 0);
lcd.print("V");
// Line 4: Temperature and Menu
lcd.setCursor(0, 3);
lcd.print("Temp:");
lcd.print(temperature, 0);
lcd.print("C ");
lcd.print(mainMenu[menuIndex]);
}
void showFaultScreen() {
lcd.setCursor(0, 0);
lcd.print("!!! FAULT DETECTED !!!");
lcd.setCursor(0, 1);
lcd.print(faultMessage);
lcd.setCursor(0, 2);
lcd.print("Check system and reset");
lcd.setCursor(0, 3);
lcd.print("Press STOP to reset");
}
void showSubMenu() {
lcd.setCursor(0, 0);
lcd.print("> ");
lcd.print(mainMenu[menuIndex]);
lcd.setCursor(0, 1);
switch(menuIndex) {
case 0: // Speed Control
lcd.print("Set Speed: ");
lcd.print(setpointSpeed, 0);
lcd.print(" RPM");
break;
case 1: // Direction
lcd.print(directionMenu[subMenuIndex]);
break;
case 2: // System Status
showDetailedStatus();
break;
default:
lcd.print("Submenu option ");
lcd.print(subMenuIndex + 1);
break;
}
}
void showDetailedStatus() {
lcd.setCursor(0, 0);
lcd.print("Detailed Status");
lcd.setCursor(0, 1);
lcd.print("Speed: ");
lcd.print(currentSpeed, 1);
lcd.print(" RPM");
lcd.setCursor(0, 2);
lcd.print("Current U: ");
lcd.print(currentU, 2);
lcd.print(" A");
lcd.setCursor(0, 3);
lcd.print("Voltage: ");
lcd.print(voltage, 1);
lcd.print(" V");
}
void showFaultMessage() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("FAULT: Cannot Start");
lcd.setCursor(0, 1);
lcd.print(faultMessage);
delay(3000);
updateDisplay();
}
// ==================== LED AND BUZZER CONTROL ====================
void updateLEDs() {
digitalWrite(LED_RUN, motorRunning);
digitalWrite(LED_FAULT, faultCondition);
digitalWrite(LED_REVERSE, reverseDirection);
}
void beep(int count) {
for (int i = 0; i < count; i++) {
digitalWrite(BUZZER, HIGH);
delay(100);
digitalWrite(BUZZER, LOW);
if (i < count - 1) delay(100);
}
}
void triggerAlarm() {
for (int i = 0; i < 5; i++) {
digitalWrite(BUZZER, HIGH);
delay(200);
digitalWrite(BUZZER, LOW);
delay(200);
}
}
// ==================== EEPROM FUNCTIONS ====================
void loadParameters() {
// Load parameters from EEPROM
// Implementation for EEPROM reading
setpointSpeed = 0.0;
}
void saveParameters() {
// Save parameters to EEPROM
// Implementation for EEPROM writing
}
// ==================== SAFETY FUNCTIONS ====================
void emergencyStop() {
sendMotorCommand("STOP");
motorRunning = false;
setpointSpeed = 0;
faultCondition = true;
faultMessage = "Emergency Stop";
updateLEDs();
triggerAlarm();
updateDisplay();
}
// ==================== COMMUNICATION PROTOCOL ====================
/*
Serial Communication Protocol:
TO MOTOR CONTROLLER:
- "START" - Start motor
- "STOP" - Stop motor
- "REVERSE" - Toggle direction
- "SET_SPD:500" - Set speed to 500 RPM
- "SET_REV:1" - Set reverse mode (1) or forward (0)
- "GET_STATUS" - Request status update
FROM MOTOR CONTROLLER:
- "STATUS:SPD123.5,RUN1,REV0,FAULT0,MOD85"
- "FAULT:Overcurrent Protection"
- "CURRENT:U3.45,V3.23"
- "VOLTAGE:350.5"
- "TEMP:45.2"
- "START_OK" - Start command acknowledged
- "STOP_OK" - Stop command acknowledged
- "REVERSE_OK" - Reverse command acknowledged
*/