cdrom_sketch_R3_11.ino
/* ########################################################################################
Simple IDE ATAPI controller using the Arduino I2C interface and
3 PCF8574 I/O expanders. Release 3.1 (adapted for Arduino 1.0)
using a 4th PCF8574 I/O expander interfacing to a 1602 LCD.
Copyright (C) 2012 Carlos Durandal
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
##########################################################################################
PCF8574 A2,A1,A0 Addr.
#1 0 0 0 0x20 interfaces to IDE DD0-DD7
#2 0 0 1 0x21 interfaces to IDE DD8-DD15
#3 0 1 0 0x22 interfaces to the IDE register selection see details below.
#4 1 1 1 0x27 interfaces to the LCD
PCF8574#3 bit: 7 6 5 4 3 2 1 0
IDE pin: nDIOR nDIOW nRST nCS1 nCS0 DA2 DA1 DA0
pin names with leading 'n' are active LOW
##########################################################################################
*/
#include <Wire.h> // I2C bus library
// This sketch uses F Malpartida's NewLiquidCrystal library. Obtain from:
// https://bitbucket.org/fmalpartida/new-liquidcrystal
#include <LiquidCrystal_I2C.h> // I2C LCD library
// Start of Definitions
// ####################
// I/O expander addresses:
const int DataL = 0x20; // IDE DD0-DD7
const int DataH = 0x21; // IDE DD8-DD15
const int RegSel = 0x22; // IDE register
const int LCD_I2C_ADDR = 0x27; // LCD (SainSmart 1602)
/* Note that the 'pins' in the following LCD definitions are not IC pins numbers
but rather specify the PCF8574 I/O port numbers: P0 to P7. E.g. LCD 'En' pin is
connected to port P2 of PCF8574 so En_pin = 2. */
const byte BACKLIGHT_PIN = 3;
const byte En_pin = 2;
const byte Rw_pin = 1;
const byte Rs_pin = 0;
const byte D4_pin = 4;
const byte D5_pin = 5;
const byte D6_pin = 6;
const byte D7_pin = 7;
LiquidCrystal_I2C lcd(LCD_I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);
// IDE Register addresses
const byte DataReg = 0xF0; // Addr. Data register of IDE device.
const byte ErrFReg = 0xF1; // Addr. Error/Feature (rd/wr) register of IDE device.
const byte SecCReg = 0xF2; // Addr. Sector Count register of IDE device.
const byte SecNReg = 0xF3; // Addr. Sector Number register of IDE device.
const byte CylLReg = 0xF4; // Addr. Cylinder Low register of IDE device.
const byte CylHReg = 0xF5; // Addr. Cylinder High register of IDE device.
const byte HeadReg = 0xF6; // Addr. Device/Head register of IDE device.
const byte ComSReg = 0xF7; // Addr. Command/Status (wr/rd) register of IDE device.
const byte AStCReg = 0xEE; // Addr. Alternate Status/Device Control (rd/wr) register of IDE device.
// Program Variables
byte dataLval; // dataLval and dataHval hold data from/to
byte dataHval; // D0-D15 of IDE
byte regval; // regval holds addr. of reg. to be addressed on IDE
byte reg; // Holds the addr. of the IDE register with adapted
// nDIOR/nDIOW/nRST values to suit purpose.
byte cnt; // packet byte counter
byte idx; // index used as pointer within packet array
byte paclen = 12; // Default packet length
byte s_trck; // Holds start track
byte e_trck; // Holds end track
byte c_trck; // Follows current track while reading TOC
byte c_trck_m; // MSF values for current track
byte c_trck_s;
byte c_trck_f;
byte a_trck = 1; // Holds actual track from reading subchannel data
byte MFS_M; // Holds actual M value from reading subchannel data
byte MFS_S; // Holds actual S value from reading subchannel data
byte d_trck; // Destination track
byte d_trck_m; // MSF values for destination track
byte d_trck_s;
byte d_trck_f;
byte aud_stat = 0xFF; // subchannel data: 0x11=play, 0x12=pause, 0x15=stop
byte asc;
long prev_millis=0;
long interval=100;
boolean toc;
// Array containing sets of 16 byte packets corresponding to part of the CD-ROM
// ATAPI function set. If the IDE device only supports packets with 12 byte length
// the last 4 bytes are not sent. The great majority of tested devices use 12 byte.
byte fnc[]= {
0x1B,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=0 Open tray
0x1B,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=16 Close tray
0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=32 Stop unit
0x47,0x00,0x00,0x10,0x28,0x05,0x4C,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=48 Start PLAY
0x4B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=64 PAUSE play
0x4B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=80 RESUME play
0x43,0x02,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=96 Read TOC
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=112 unit ready
0x5A,0x00,0x01,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=128 mode sense
0x42,0x02,0x40,0x01,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=144 rd subch.
0x03,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // idx=160 req. sense
0x4E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 // idx=176 Stop disk
};
// Arduino pin assignments:
const byte LED = 13;
const byte NEXT = 12; // NEXT button
const byte EJCT = 11; // EJECT button, open/close CD-ROM tray
const byte STOP = 10; // STOP button
const byte PLAY = 9; // PLAY button
const byte PREV = 8; // PREV button
// End of Definitions ########################################################################
void setup(){
// LCD Part
// ########
lcd.begin (16,2); // init LCD interface
// Switch on the backlight
lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
lcd.setBacklight(HIGH);
lcd.home (); // set cursor to home position
// Arduino Part
// #############
// start I2C interface as Master
Wire.begin();
// Set all pins of all PCF8574 to high impedance inputs.
highZ();
// Start Serial Interface
// Serial.begin(9600);
// initialize the push button pins as inputs with pullup:
pinMode(NEXT, INPUT);
pinMode(PREV, INPUT);
pinMode(EJCT, INPUT);
pinMode(STOP, INPUT);
pinMode(PLAY, INPUT);
pinMode(LED, OUTPUT);
digitalWrite((byte)NEXT, HIGH);
digitalWrite((byte)PREV, HIGH);
digitalWrite((byte)EJCT, HIGH);
digitalWrite((byte)STOP, HIGH);
digitalWrite((byte)PLAY, HIGH);
digitalWrite((byte)LED, LOW);
// IDE Initialisation Part
// ########################
lcd.print("Atapiduino");
lcd.setCursor (0,1); // cursor to beginning of LCD 2nd. line
lcd.print("Release 3.1");
reset_IDE(); // Do hard reset
delay(3000); // This delay waits for the drive to initialise
BSY_clear_wait(); // The ATAPI spec. allows drives to take up to
DRY_set_wait(); // 31 sec. but all tested where alright within 3s.
lcd.clear();
readIDE(CylLReg); // Check device signature for ATAPI capability
if(dataLval == 0x14){
readIDE(CylHReg);
if(dataLval == 0xEB){
lcd.print("Found ATAPI Dev.");
}
}else{
lcd.print("No ATAPI Device!");
while(1); // No need to go ahead.
}
writeIDE(HeadReg, 0x00, 0xFF); // Set Device to Master (Device 0)
// Initialise task file
// ####################
init_task_file();
// Run Self Diagnostic
// ###################
delay(3000);
lcd.clear ();
lcd.print("Self Diag. ");
writeIDE(ComSReg, 0x90, 0xFF); // Issue Run Self Diagnostic Command
readIDE(ErrFReg);
if(dataLval == 0x01){
lcd.print("OK");
}else{
lcd.print("Fail"); // Units failing this may still work fine
}
delay(3000);
lcd.clear ();
lcd.print("ATAPI Device:");
lcd.setCursor (0,1);
// Identify Device
// ###############
writeIDE (ComSReg, 0xA1, 0xFF); // Issue Identify Device Command
delay(500); // Instead of wait for IRQ. Needed by some dev.
// readIDE(AStCReg); //
do{
readIDE(DataReg);
if (cnt == 0){ // Get supported packet lenght
if(dataLval & (1<<0)){ // contained in lower byte of first word
paclen = 16; // 1st bit set -> use 16 byte packets
}
}
if(cnt > 26 & cnt < 47){ // Read Model
lcd.write(dataHval);
lcd.write(dataLval);
// Serial.print(dataHval);
// Serial.print(dataLval);
}
cnt++;
readIDE(ComSReg); // Read Status Register and check DRQ,
} while(dataLval & (1<<3)); // skip rest of data until DRQ=0
readIDE(AStCReg);
DRQ_clear_wait();
// Check if unit ready
// ###################
unit_ready(); // Send packet 'test unit ready'
req_sense(); // Send packet 'Request Sense'
if(asc == 0x29){ // Req. Sense returns 'HW Reset'
unit_ready(); // (ASC=29h) at first since we had one.
req_sense(); // New Req. Sense returns if media
} // is present or not.
do{
unit_ready(); // Wait until drive is ready.
req_sense(); // Some devices take some time
}while(asc == 0x04); // ASC=04h -> LOGICAL DRIVE NOT READY
}
// ############
// End of setup
// ############
// This part reads the push buttons, checks device audio status, interprets operator commands
// and displays the corresponding data depending on the status and/or the commands resulting
// from pressing the push buttons.
void loop(){
// Scan push buttons
if(digitalRead(EJCT) == LOW){
lcd.clear();
toc=false; // Set toc invalid
switch(chck_disk()) {
case 0x00: // If disk in tray case
lcd.print(" OPEN");
eject();
break;
case 0xFF: // If tray closed but no disk in case
eject();
lcd.print(" OPEN");
break;
case 0X71: // If tray open -> close it
lcd.print(" LOAD");
load();
}
lcd.clear();
a_trck = s_trck; // Reset to start track
}
if(digitalRead(STOP) == LOW){
a_trck=s_trck; // Reset to start track
stop_disk(); // Stop Disk
stop(); // Stop unit
toc=false;
}
if(digitalRead(PLAY) == LOW){ // Play has been pressed
switch(aud_stat){
case 0x15: // If stopped
play(); // start play
break;
case 0x12: // if paused
resume(); // resume play
break;
case 0x11: // if playing
pause(); // pause playback
}
toc=false; // mark TOC unknown in case disk
lcd.clear(); // is removed using device eject buton
} // while play in progress
if(digitalRead(NEXT) == LOW){
a_trck = a_trck + 1; // a_track becomes next track
if(a_trck > e_trck){(a_trck = s_trck);} // over last track? -> point to start track
get_TOC(); // Get MSF for a_trck
fnc[51] = d_trck_m; // Store new play start position
fnc[52] = d_trck_s; // in play packet and start play
fnc[53] = d_trck_f;
play();
if(aud_stat == 0x12 | // If paused or stopped -> pause
aud_stat == 0x15)
{
pause();
}
lcd.clear();
}
if(digitalRead(PREV) == LOW){ // Basically like the NEXT function above
a_trck = a_trck - 1; // only backwards
if(a_trck < s_trck){(a_trck = e_trck);}
get_TOC();
fnc[51] = d_trck_m;
fnc[52] = d_trck_s;
fnc[53] = d_trck_f;
play();
if(aud_stat == 0x12 |
aud_stat == 0x15)
{
pause();
}
lcd.clear();
}
if(millis()-prev_millis > interval){ // This part will periodically check the
read_subch_cmd(); // current audio status and update the display
if(aud_stat==0x11){ // accordingly.
lcd.home();
lcd.print(" PLAY ");
curr_MSF(); // Display pickup position
}
if(aud_stat==0x12){
lcd.home();
lcd.print(" PAUSE ");
curr_MSF();
}
if(aud_stat==0x15 & !toc){ // If stopped and TOC invalid
get_TOC(); // try to read TOC
Disp_CD_data(); // display TOC data and set TOC valid
toc=true; // to prevent reading over and over
}
if(aud_stat==0x00){ // Audio status 0 covers all other posible
lcd.clear(); // states not decoded by this sketch and
lcd.print(" NO DISC"); // handles them as NO DISC.
}
prev_millis=millis();
}
}
// #######################################
// Auxiliary functions for displaying data
// #######################################
void Disp_CD_data(){ // Used to display track range and
lcd.clear(); // Total playing time as recovered
lcd.print("Tracks "); // from reading the TOC
lcd.print(s_trck, DEC);
lcd.print("-");
lcd.print(e_trck, DEC);
lcd.setCursor (0,1);
lcd.print("Time ");
lcd.print(fnc[54], DEC);
lcd.print(":");
if(fnc[55] < 10)
{
lcd.print("0"); // Print a leading 0 for seconds when below 10
}
lcd.print(fnc[55], DEC);
}
void curr_MSF(){ // During PLAY or PAUSE operation show the pickup
lcd.setCursor (2,1); // position as absolute playing time
lcd.print(a_trck, DEC);
lcd.setCursor (11,1);
lcd.print(MFS_M,DEC);
lcd.print(":");
if(MFS_S < 10)
{
lcd.print("0"); // Print a leading 0 for seconds when below 10
}
lcd.print(MFS_S,DEC);
}
// ##################################
// Auxiliary functions User Interface
// ##################################
void play(){
idx = 48; // pointer to play function and Play
SendPac(); // from MSF location stored at idx=(51-56)
} // See also doc. sff8020i table 76
void stop(){
idx = 32; // pointer to stop unit function
SendPac();
}
void eject(){
idx = 0; // pointer to eject function
SendPac();
}
void load(){
idx = 16; // pointer to load
SendPac();
}
void pause(){
idx = 64; // pointer to hold
SendPac();
}
void resume(){
idx = 80; // pointer to resume
SendPac();
}
void stop_disk(){
idx = 176; // pointer to stop disk function
SendPac();
}
// ###########################
// Auxiliary functions PCF8475
// ###########################
// Set to high impedance all ports of PCF8475 interfacing to IDE.
void highZ(){
Wire.beginTransmission(RegSel); // address IDE Register interface
Wire.write((byte)255); // queue FFh into buffer for setting all pins HIGH
Wire.endTransmission(); // transmit buffered data to IDE Register interface
Wire.beginTransmission(DataH); // address IDE DD8-DD15
Wire.write((byte)255); // as above
Wire.endTransmission(); //
Wire.beginTransmission(DataL); // address IDE DD0-DD7
Wire.write((byte)255); // as above
Wire.endTransmission(); //
}
// Reset Device
void reset_IDE(){
Wire.beginTransmission(RegSel);
Wire.write((byte)B11011111); // Bit 5 LOW to reset IDE via nRESET
Wire.endTransmission();
delay(40);
Wire.beginTransmission(RegSel);
Wire.write((byte)B11111111); // Release reset
Wire.endTransmission();
delay(20);
}
// Read one word from IDE register
void readIDE (byte regval){
reg = regval & B01111111; // set nDIOR bit LOW preserving register address
Wire.beginTransmission(RegSel);
Wire.write((byte)reg);
Wire.endTransmission();
Wire.requestFrom(DataH, 1);
dataHval = Wire.read();
Wire.requestFrom(DataL, 1);
dataLval = Wire.read();
highZ(); // set all I/O pins to HIGH -> impl. nDIOR release
}
// Write one word to IDE register
void writeIDE (byte regval, byte dataLval, byte dataHval){
reg = regval | B01000000; // set nDIOW bit HIGH preserving register address
Wire.beginTransmission(RegSel);
Wire.write((byte)reg);
Wire.endTransmission();
Wire.beginTransmission(DataH); // send data for IDE D8-D15
Wire.write((byte)dataHval);
Wire.endTransmission();
Wire.beginTransmission(DataL); // send data for IDE D0-D7
Wire.write((byte)dataLval);
Wire.endTransmission();
reg = regval & B10111111; // set nDIOW LOW preserving register address
Wire.beginTransmission(RegSel);
Wire.write((byte)reg);
Wire.endTransmission();
highZ(); // All I/O pins to high impedance -> impl. nDIOW release
}
// #################################################
// Auxiliary functions ATAPI Status Register related
// #################################################
// Wait for BSY clear
void BSY_clear_wait(){
do{
readIDE(ComSReg);
} while(dataLval & (1<<7));
}
// Wait for DRQ clear
void DRQ_clear_wait(){
do{
readIDE(ComSReg);
} while(dataLval & (1<<3));
}
// Wait for DRQ set
void DRQ_set_wait(){
do{
readIDE(ComSReg);
}while((dataLval & ~(1<<3)) == true);
}
// Wait for DRY set
void DRY_set_wait(){
do{
readIDE(ComSReg);
}while((dataLval & ~(1<<6)) == true);
}
// ##################################
// Auxiliary functions Packet related
// ##################################
// Send a packet starting at fnc array position idx
void SendPac(){
writeIDE (AStCReg, B00001010, 0xFF); // Set nIEN before you send the PACKET command!
writeIDE(ComSReg, 0xA0, 0xFF); // Write Packet Command Opcode
delay(400);
for (cnt=0;cnt<paclen;cnt=cnt+2){ // Send packet with length of 'paclen'
dataLval = fnc[(idx + cnt)]; // to IDE Data Registeraccording to idx value
dataHval = fnc[(idx + cnt + 1)];
writeIDE(DataReg, dataLval, dataHval);
readIDE(AStCReg); // Read alternate stat reg.
readIDE(AStCReg); // Read alternate stat reg.
}
BSY_clear_wait();
}
void get_TOC(){
idx = 96; // Pointer to Read TOC Packet
SendPac(); // Send read TOC command packet
delay(10);
DRQ_set_wait();
read_TOC(); // Fetch result
}
void read_TOC(){
readIDE(DataReg); // TOC Data Length not needed, don't care
readIDE(DataReg); // Read first and last session
s_trck = dataLval;
e_trck = dataHval;
do{
readIDE(DataReg); // Skip Session no. ADR and control fields
readIDE(DataReg); // Read curent track number
c_trck = dataLval;
readIDE(DataReg); // Read M
c_trck_m = dataHval; // Store M of curent track
readIDE(DataReg); // Read S and F
c_trck_s = dataLval; // Store S of current track
c_trck_f = dataHval; // Store F of current track
if (c_trck == s_trck){ // Store MSF of first track
fnc[51] = c_trck_m; //
fnc[52] = c_trck_s;
fnc[53] = c_trck_f;
}
if (c_trck == a_trck){ // Store MSF of actual track
d_trck_m = c_trck_m; //
d_trck_s = c_trck_s;
d_trck_f = c_trck_f;
}
if (c_trck == 0xAA){ // Store MSF of end position
fnc[54] = c_trck_m;
fnc[55] = c_trck_s;
fnc[56] = c_trck_f;
}
readIDE(ComSReg);
} while(dataLval & (1<<3)); // Read data from DataRegister until DRQ=0
}
void read_subch_cmd(){
idx=144; // Pointer to read Subchannel Packet
SendPac(); // Send read Subchannel command packet
readIDE(DataReg); // Get Audio Status
if(dataHval==0x13){ // Play operation successfully completed
dataHval=0x15; // means drive is neither paused nor in play
} // so treat as stopped
if(dataHval==0x11| // playing
dataHval==0x12| // paused
dataHval==0x15) // stopped
{aud_stat=dataHval; //
}else{
aud_stat=0; // all other values will report "NO DISC"
}
readIDE(DataReg); // Get (ignore) Subchannel Data Length
readIDE(DataReg); // Get (ignore) Format Code, ADR and Control
readIDE(DataReg); // Get actual track
a_trck = dataLval;
readIDE(DataReg); // Get M field of actual MFS data and
MFS_M = dataHval; // store M it
readIDE(DataReg); // get S and F fields
MFS_S = dataLval; // Store S value
do{
readIDE(DataReg);
readIDE(ComSReg);
} while(dataLval & (1<<3)); // Read rest of data from Data Reg. until DRQ=0
}
byte chck_disk(){
byte disk_ok = 0xFF; // assume no valid disk present.
idx = 128; // Send mode sense packet
SendPac(); //
delay(10);
DRQ_set_wait(); // Wait for data ready to read.
readIDE(DataReg); // Read and discard Mode Sense data length
readIDE(DataReg); // Get Medium Type byte
// If valid audio disk present disk_ok=0x00
if (dataLval == 0x02 |
dataLval == 0x06 |
dataLval == 0x12 |
dataLval == 0x16 |
dataLval == 0x22 |
dataLval == 0x26)
{disk_ok = 0x00;
}
if (dataLval == 0x71){ // Note if door open
disk_ok = 0x71;
}
do{ // Skip rest of packet
readIDE(DataReg);
readIDE(ComSReg);
} while(dataLval & (1<<3));
return(disk_ok);
}
void unit_ready(){ // Reuests unit to report status
idx=112; // used to check_unit_ready
SendPac();
}
void req_sense(){ // Request Sense Command is used to check
idx=160; // the result of the Unit Ready command.
SendPac(); // The Additional Sense Code is used,
delay(10); // see table 71 in sff8020i documentation
DRQ_set_wait();
cnt=0;
do{
readIDE(DataReg);
if (cnt == 6){
asc=dataLval; // Store Additional Sense Code
}
cnt++;
readIDE(AStCReg);
readIDE(ComSReg);
} while(dataLval & (1<<3)); // Skip rest of packet
}
void init_task_file(){
writeIDE(ErrFReg, 0x00, 0xFF); // Set Feature register = 0 (no overlapping and no DMA)
writeIDE(CylHReg, 0x02, 0xFF); // Set PIO buffer to max. transfer length (= 200h)
writeIDE(CylLReg, 0x00, 0xFF);
writeIDE(AStCReg, 0x02, 0xFF); // Set nIEN, we don't care about the INTRQ signal
BSY_clear_wait(); // When conditions are met then IDE bus is idle,
DRQ_clear_wait(); // this check may not be necessary (???)
}
// END ####################################################################################