/*
There are few parts to this project and it stilll very much a work in progress.
The first part is to emulate an E6 CP-AG5C-C Absolute encoder.
That component outputs open collector gray code.
The second part iis to hardware convert the grey code back to binary.
This is simply to check that my logic all works out.
This offloads a Gray conversion into hardware.
The next part of the project is to switch on and off the motor by reading the Gray code coming out of the absolute encoder.
To simmulate the encoder moving in time with the motor I have connected
the direction control and motor enable pins to a logic circuit
that performs a simple 8-bit up-and-down counter.
There is a 4x5 keyboard which will be used to enter the coordinates to stop or start the encoder.
And future development will bbe to add cues sso you can step through predefined movemeennts of a mmotor.
Ultimately this will be the control unit for a revolving stage floor
*/
#include <LiquidCrystal_I2C.h> // LCD Library
#include <EEPROM.h> // EEPROM library
#include <Wire.h> // I2c Communication Library
#include <Keypad.h> // Keypad Library
#define dirPin 11 // Direction pin control for A4988
#define stepPin 12 // Stepper pin control for A4988
#define M_EnablePin 13 // Motor enable/Disable pin for A4988
#define SpeedPin A0 // temp will using encoder instead of this pin
LiquidCrystal_I2C lcd(0x27, 20, 4);
//Co-ordernates data entry // still woring on data entry
byte BinInput = 0;
byte NumKeys[4];
byte LcdPos= 6;
byte lcdPointer = 0;
// Keyboard Setup 4x5
const byte ROWS = 5;
const byte COLS = 4;
char Keys[ROWS][COLS] =
{
'A', 'B', '#', '*',
'1', '2', '3', 'C',
'4', '5', '6', 'D',
'7', '8', '9', 'E',
'F', '0', 'G', 'H'
};
byte rowPins[ROWS] = {6,7,8,9,10};
byte colPins[COLS] = {2,3,4,5};
Keypad keypad = Keypad( makeKeymap(Keys), rowPins, colPins, ROWS, COLS );
unsigned int PC_Count=0; // Programmed Cues - current memory pointer
//----- Rotary endoder setup
#define ENCODER_CLK 19 // Rotary encoder button CLK
#define ENCODER_DT 18 // Rotary encoder button DT
#define ENCODER_SW 17 // Rotary encoder button SW
int lastClkState;
unsigned long lastDebounce = 0;
const unsigned long DEBOUNCE_MS = 50 ;
int lastSwState;
// set default limits for the encoder
byte UpperLimit = 255; // Limit the Rencode top count (default)
byte LowerLimit = 0; // Limit the Rencode bottom count (Default)
byte Edit_Mode = 0; // What mode the encoder is currently adjusting
//-------------------------------------
struct DataStruct
{ // Used for Saving & retreiving cues from the EEPROM
byte ENCODER; // 0-255
byte DIRECTION; // 0 or 1
byte SPEED; // 0-99
byte ROTATIONS; //0-50
};
struct DataStruct CueData;
byte dataSize = sizeof(DataStruct);
byte EN_Current; // 0-155
byte DIR_Current; // 0 or 1
byte SPEED_Current; // 0-99
byte Rotations_Current;
byte EN_New; // 0-155
byte DIR_New; // 0 or 1
byte SPEED_New; // 0-99
byte ROTATIONS_New; //into next cue 0-50
void ModeSelect(void)
{ // Toggle the edit mode with each press of the encoder switch
Edit_Mode ++;
if(Edit_Mode > 4 ){
Edit_Mode = 0;
}
}
void dummydata(void) // just used for this emulator not real world
{ // just for tesing in this sim
LcdPrintS(0,0,"Adding EEPROM DATA.." );
for(byte x = 0; x < 255; x++){
byte e = random(0,255);
byte d = random(0,1);
byte s = random(0,99);
byte r = random(0,50);
PC_Count = x;
CueUpdate(e,d,s,r);
// delay(50); // Just for testing
}
PC_Count = 0; // Reset Cue counter to 0;
}
// LCD Display
void EnUpdate(byte line, byte Value)
{ // LCD Print Encoder Value
LcdPrintS(0,line,"E: ");
LcdPrintB(2,line,Value);
}
void DirUpdate(byte line, byte Value)
{ // Lcd Print Direction
LcdPrintS(6,line,"D: ");
if(Value == 0) {
LcdPrintS(8,line,"AC");
} else {
LcdPrintS(8,line,"CW"); // AC
}
}
void SpeedUpdate(byte line, byte Value)
{ // Lcd Print Speed
LcdPrintS(11,line,"S: ");
if(Value > 99){
LcdPrintB(13,line,99);
} else {
LcdPrintB(13,line,Value);
}
}
void RotationsUpdate(byte line, byte Value)
{ // LCD Print Rotation before stop
LcdPrintS(16,line,"R: ");
if(Value > 99){
LcdPrintB(18,line,99);
} else {
LcdPrintB(18,line,Value);
}
}
void CueUpdate(byte e,byte d,byte s,byte r) // Assume, (PC_count Global )is the address * struct size
{ // Update the EEPROM at the current cue with new values
CueData = (DataStruct) {e,d,s,r};
int eeaddress = PC_Count * sizeof(CueData);
EEPROM.put(eeaddress, CueData);
CueStatus(PC_Count); // update line 1 of the LCD with the retreived eprom values
}
void CueStatus(byte cueNumber)
{ // Retreive the eprom data and display on line one of the LCD
int eeaddress = cueNumber * sizeof(dataSize); // Calculate the eeprom location of the current Cue
EEPROM.get(eeaddress, CueData); // Retreive all the varibles and place into the CueData Struct
EnUpdate(1,CueData.ENCODER); // Display encoder data
DirUpdate(1,CueData.DIRECTION); // Direction data
SpeedUpdate(1,CueData.SPEED); // Display Speed data
RotationsUpdate(1,CueData.ROTATIONS); // Display encoder data
PC_Update(); // Update the PC (Program counter)
}
void pulseWidth(byte msVal)
{ // Set the Stepper pulse width 0-4 input
byte mask = B11111000;
PORTA = (PORTA & mask) | msVal;
/*
MS3 MS2 MS1
0 0 0 Full Step
0 0 1 Half Step
0 1 0 Quarter Step
0 1 1 Eight Step
1 0 0 Sixteenth Step
*/
}
byte byte2ascii(char ascii[4] )
{ //convert a byte into 3-digit ASCII string
byte value;
sprintf(ascii, "%03d",value);
return value;
}
void EStop(void)
{ // Disable motor output
digitalWrite(M_EnablePin,HIGH);
}
void EStart(void)
{ // Enable motor output
digitalWrite(M_EnablePin,LOW);
}
void LcdPrintS(byte x,byte y,String message)
{ // Print a string to the LCD at the coordinates
lcd.setCursor(x,y);
lcd.print(message);
}
void LcdPrintB(byte x,byte y,byte message)
{// Print a byte to the LCD at the coordinates
lcd.setCursor(x,y);
lcd.print(message);
}
byte GetSpeed(void)
{ // read the speed value from the pot map value to 0-99
int val = analogRead(SpeedPin);
return map(val, 0,1023,0,99);
}
byte GetDir(void) {
return digitalRead(dirPin);
}
void setDir(byte dirVal)
{ // Set the direction port
digitalWrite(dirPin,dirVal);
}
void motorStatus(void)
{ // Display the motor Status Live/Offg
LcdPrintS(0,3,"Motor:");
if(digitalRead(M_EnablePin) == 0){
LcdPrintS(6,3,"LIVE");
} else {
LcdPrintS(6,3,"OFF ");
}
}
void PCountUP(void)
{ // Inrement the progrom counter
if(PC_Count<255) {
PC_Count ++;
} else {
PC_Count = 0;
}
CueStatus(PC_Count);
PC_Update();
}
void PCountDN(void)
{ // Decriment the program counter
if(PC_Count> 0) {
PC_Count --;
} else {
PC_Count = 255;
}
CueStatus(PC_Count);
PC_Update();
}
void PC_Update(void)
{ // Display the program counter
LcdPrintS(14,3,"PC: ");
LcdPrintB(17,3,PC_Count);
}
byte EnLocation(void)
{ // Read port L for encoder value
return PINL;
}
void EnStatus(void)
{ // Get current absolute encoder location and send to LCD line 0
EnUpdate(0,EnLocation() );
}
void Mstep(void)
{ // Moce the Stepper by one step
EStart();
delayMicroseconds(20);
digitalWrite(stepPin,HIGH);
delayMicroseconds(20);
digitalWrite(stepPin,LOW);
}
void title(void)
{ // Acknowledgement for designer
lcd.clear();
LcdPrintS(0,0,"Motor/Encoder Sim");
LcdPrintS(0,1,"J Paewai (R) 2205");
delay(2000);
}
void CurrentUpdate(void)
{ // Display the current position and settings
EnUpdate(0,EnLocation() ); // Display current endoder location
DirUpdate(0,GetDir() ); // display direction
SpeedUpdate(0, GetSpeed() ); //
PC_Update(); // Update the program counter
motorStatus(); // Stat 1 line 1
}
void RunCue()
{ // only temp to test
int eeaddress = PC_Count * sizeof(dataSize); // Calculate the eeprom location of the current Cue
EEPROM.get(eeaddress, CueData); // Retreive all the varibles and place into the CueData Struct
setDir(CueData.DIRECTION); // set the direction of travel
byte CurrentENC = EnLocation(); // Actual encoder location
if(CueData.DIRECTION >1) {
// process multiple revolutions
// from the current position
} else {
// set the speed using the CueData.SPEED varible
EStart();
do{
Mstep(); // Execute motor step
EnStatus(); // Display currect location
}while(CueData.ENCODER != EnLocation() && keypad.getKey() != 'A'); // Stay in loop until encoder matches target location or Estop pressed
EStop();
}
}
void ProcessButton(void)
{ // Process the encoder putton presses
int swState = digitalRead(ENCODER_SW);
if(swState != lastSwState) {
unsigned long now = millis();
if(now - lastDebounce > DEBOUNCE_MS && swState == LOW) {
// save the current mode settings struct
ModeSelect();
// updateDisplay
}
lastDebounce = now;
}
lastSwState = swState;
}
void setup()
{
randomSeed(analogRead(A1)); // Temp set random seed for ranmdom numbers generator
lcd.begin(20,4); // Configure LCD
lcd.clear(); // Clear screen jut incase of any lcd clitch
title(); // Acknowledgement
pinMode(dirPin,OUTPUT); // Direction
pinMode(stepPin,OUTPUT); // Step
pinMode(M_EnablePin,OUTPUT); // Enable/Disable Disable is HIGH
pinMode(SpeedPin, INPUT); // Pot Adjust motor speed
digitalWrite(dirPin, HIGH); // CCW
digitalWrite(stepPin, LOW); // Active on Leaging edge high
digitalWrite(M_EnablePin,HIGH); //Active Low
pinMode(ENCODER_CLK, INPUT_PULLUP); // Rotary encoder seup
pinMode(ENCODER_DT, INPUT_PULLUP); // Rotary encoder seup
pinMode(ENCODER_SW, INPUT_PULLUP); // not sure how but is configured yet
DDRA = DDRA | B00000111; // A4988 MS1,MS2,MS3 Pins Control outputs Pins 22-24
DDRL = DDRL | B00000000; // E6CP-AG5C-C Encoder Read INPUTS Pins 42-49
lcd.clear(); // Clear screen after title screen
dummydata(); // Populate some dummy EEPROM DATA
lcd.clear(); // Clear the lcd screen
CurrentUpdate(); // Current settings
CueStatus(PC_Count); // Display line 2 , EEPROM date from current cue
Edit_Mode = 0; // 1=speed,1=encoder,2,dir, 3= rotations
lastClkState = digitalRead(ENCODER_CLK);
}
void loop()
{
char key = keypad.getKey();
if (key){ // Process keypad
switch (key)
{
case 'A': EStop(); motorStatus(); // Emergency Stop (F1)
break;
case 'B': EStart(); motorStatus(); // Enable Motor (F2)
break;
case '#': digitalWrite(dirPin, !digitalRead(dirPin)); DirUpdate(0,GetDir()); // direction CW / CCW (#)
break;
case '*': EnStatus(); // (*)
break;
case 'C': PCountUP(); // (UP Arrow)
break;
case 'D': PCountDN(); // (Down Arrow)
break;
case 'E': // (ESCAPE KEY)
break;
case 'F': // (Left arrow)
break;
case 'G': // (Right Arrow)
break;
case 'H': RunCue(); EnStatus(); // (Enter Key)
break; // run Function
case '0':break;
case '1':break;
case '2':break;
case '3':break;
case '4':break;
case '5':break;
case '6':break;
case '7':break;
case '8':break;
case '9':break;
break;
}
// process Encoder rotations
}
ProcessButton();
EnUpdate(0,EnLocation() );
SpeedUpdate(0,GetSpeed() );
// live data has no rotations reference as it refers to the next cue
}
void ClearNumbers(void){
// for(byte r=5;r<8;r++) LcdPrintS(r,3," ");
}
8 Bit Binary up/down Counter, Output converted to Gray, then Gray converted to Binary. To simulate the Omrom E6CP-AG5C-C Encoder
Clock input with clock enable
Up/down control, High Count up,
Low Count down
Gray to Bin Converter
Bin to Gray converter
BIN MSB
ESTOP
M Enable
Toggle DIR
1
2
3
4
5
6
7
8
9
Next Cue
Previous Cue
Save Cue
go left
0
go right
EXECUTE
CW=HIGH
CCW=LOW
Enable=LOW
Disable=HIGH
Step Clock +
Encoder Clock
LOW to enable clock
HIGH=Count UP
LOW=Count Down
MSI Port Control Monitor
Speed Set 0-99
Rotations
10K/2
5K/2
ERC Warnings
flop15:CLK: Input pin not driven
flop1:CLK: Clock driven by combinatorial logic
flop2:CLK: Clock driven by combinatorial logic
flop3:CLK: Clock driven by combinatorial logic
flop4:CLK: Clock driven by combinatorial logic
flop5:CLK: Clock driven by combinatorial logic
flop6:CLK: Clock driven by combinatorial logic
flop7:CLK: Clock driven by combinatorial logic
flop8:CLK: Clock driven by combinatorial logic
flop9:CLK: Clock driven by combinatorial logic