https://hades.mech.northwestern.edu//api.php?action=feedcontributions&user=Thomas+Peterson&feedformat=atomMech - User contributions [en]2024-03-29T11:55:58ZUser contributionsMediaWiki 1.35.9https://hades.mech.northwestern.edu//index.php?title=Monkeybot_Circuit_and_Program_Documentation&diff=18756Monkeybot Circuit and Program Documentation2010-06-16T21:16:33Z<p>Thomas Peterson: New page: Documentation for Monkeybot 32 electronics and program: Monkeybot writeup</p>
<hr />
<div>Documentation for Monkeybot 32 electronics and program: [[Media:Monkeybot_writeup.pdf | Monkeybot writeup]]</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:Monkeybot_writeup.pdf&diff=18755File:Monkeybot writeup.pdf2010-06-16T21:15:10Z<p>Thomas Peterson: </p>
<hr />
<div></div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18375Using the LS7366R SPI Quadrature Counter2010-05-03T23:18:24Z<p>Thomas Peterson: /* 7366_decoder.c */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.<br />
<br />
The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:<br />
<br />
*MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more<br />
*MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags<br />
*DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.<br />
*CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.<br />
*OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read '''the value of CNTR at the time of the last CNTR read'''.<br />
*STR- This is a status register containing status bits<br />
*IR- This register is used internally to select the register being written to or read from on the next bus transaction.<br />
<br />
Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.<br />
<br />
==Circuit==<br />
[[Image:7366_circuit1.PNG|thumb|300px|right]]<br />
<br />
The 7366 connects to the PIC using the SPI bus, as mentioned above. As seen in the circuit to the right, the relevant wires are SCK, SDI, SDO, and SS. In the code below, the SPI1 module is used, but with slight changes the SPI2 module could be used instead. There are other optional pins that could be connected for interrupts, etc., for more information see the 7366 datasheet.<br />
<br />
As seen in the diagram, the 7366 requires an external clock. Instead of using a crystal as shown, one could also supply an external clock pulse to the fCKI pin. For more information and frequency requirements, see the datasheet.<br />
<br />
==Code==<br />
The code consists of 4 files:<br />
*7366.h: A header file containing various defines<br />
*7366_lib.c: A C file containing functions that interact with the 7366<br />
*7366_decoder.c: A C file containing main() and a simple test program<br />
<br />
All three can be downloaded [[Media:7366R.zip| here]].<br />
===7366_lib.c===<br />
This file contains library functions for using the LS7366R. The most relavent functions are enable_7366(), write_7366(), and read_7366(). The configuration flags are listed in the 7366.h file. Note that the read and write functions are passed pointers to arrays to read from or write to. This uses the SPI1 module.<br />
<br />
/********************************************************************<br />
* 7366_lib.c : Library functions for using the 7366R SPI decoder<br />
*<br />
* Creation date: 16-APR-2010<br />
* Last modified: 27-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "_spi_map_tbl.h"<br />
#include "7366.h"<br />
<br />
<br />
/* enable_7366(): enables counting on 7366, sends configuration words<br />
and optionally turns on flags */<br />
void enable_7366(int config1, int flags) {<br />
unsigned char writebyte;<br />
writebyte = config1; //config word 1<br />
write_7366(MDR0,&writebyte);<br />
writebyte = flags | (4-COUNTER_BYTES); //config word 2<br />
write_7366(MDR1, &writebyte);<br />
}<br />
<br />
void disable_7366_counting() {<br />
//send disable to MDR1<br />
unsigned char writebyte;<br />
writebyte = COUNTER_BYTES;<br />
write_7366(MDR1, &writebyte);<br />
}<br />
<br />
/* write_7366(): Writes bytes in array "bytearray" to register "reg".<br />
The number of bytes written depends on the reister and mode.<br />
Config registers will write 1 byte. Other registers will<br />
write the number of bytes specified by COUNTER_BYTES */<br />
void write_7366(int reg,unsigned char *bytearray) {<br />
unsigned char ir = (0x2 << 6 ) | (reg << 3); //Instruction<br />
unsigned char ReadData;<br />
if ( (reg == MDR0) || (reg == MDR1) || (reg == STR) ) {<br />
//One byte to write<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
SpiChnPutC(SPICHN, bytearray[0]); //Clock out write byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (garbage /dont care)<br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
if ( (reg == DTR) || (reg == CNTR) || (reg == OTR) ) {<br />
//1-4 bytes to read<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
//Do reads<br />
int i;<br />
for (i=0;i<COUNTER_BYTES;i++) {<br />
SpiChnPutC(SPICHN, bytearray[i]); //Clock out byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (don't care)<br />
} <br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
}<br />
<br />
<br />
/* read_7366(): Reads bytes to array "bytearray" from register "reg".<br />
The number of bytes read depends on the reister and mode.<br />
Config registers will read 1 byte. Other registers will<br />
read the number of bytes specified by COUNTER_BYTES */<br />
void read_7366(int reg, unsigned char *bytearray) {<br />
unsigned char ir = (0x1 << 6 ) | (reg << 3); //Instruction<br />
unsigned char ReadData;<br />
if ( (reg == MDR0) || (reg == MDR1) || (reg == STR) ) {<br />
//One byte to read<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
SpiChnPutC(SPICHN, 0); //Clock out dummy byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (register)<br />
*bytearray = ReadData;<br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
if ( (reg == DTR) || (reg == CNTR) || (reg == OTR) ) {<br />
//1-4 bytes to read<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
//Do reads<br />
int i;<br />
for (i=0;i<COUNTER_BYTES;i++) {<br />
SpiChnPutC(SPICHN, 0); //Clock out dummy byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (register)<br />
bytearray[i] = ReadData;<br />
} <br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
}<br />
<br />
/* clear_reg_7366(): Clears the given register */<br />
void clear_reg_7366(int reg) {<br />
char ReadData;<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
char ir = (reg << 3); //Instruction<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
SLAVE_SELECT = 1;<br />
}<br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
===7366_decoder.c===<br />
This file contains a simple test function that sets up the counter and reads from it periodically. Note that it sets up the 7366 using the enable_7366 function and sets it up in the x4 and modulo-n modes, which are generally used for motors. In modulo-n mode, the encoder high count value (the number of tics in a full rotation) is loaded into DTR, as seen.<br />
<br />
During the main loop, the program calls read_7366(CNTR, readbuf), which reads the current counter value by writing it into readbuf. Note that since the counter is in 3 byte mode (as defined in the 7366.h file) the function will read 3 bytes into the array pointed to by readbuf.<br />
<br />
/********************************************************************<br />
* 7366_decoder.c : Interface between the 7366R decoder by LSI/CSI<br />
* and the PIC32. The 7366R uses the SPI interface<br />
* to communicate with the microcontroller.<br />
*<br />
* Creation date: 15-APR-2010<br />
* Last modified: 27-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7366.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//RF13 has been set to slave select in 7366.h<br />
TRISFbits.TRISF13 = 0; //Output for slave select<br />
SLAVE_SELECT = 1; //Start unselected (slave select is active low)<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//SPI setup<br />
int rData = SPI1BUF; //Clears receive buffer<br />
IFS0CLR = 0x03800000; //Clears any existing event (rx / tx/ fault interrupt)<br />
SPI1STATCLR = 0x40; //Clears overflow<br />
//Enables the SPI channel (channel, master mode enable | use 8 bit mode | turn on, clock divider)<br />
SpiChnOpen(1, SPI_CON_MSTEN | SPI_CON_MODE8 | SPI_CON_ON|SPI_CKE_ON, 128); // divide fpb by 128, configure the I/O ports.<br />
<br />
//Init 7366R<br />
enable_7366(QUADRATURE_X4 | MODULO_N | DISABLE_INDX,NO_FLAGS);<br />
<br />
unsigned char writebuf[COUNTER_BYTES];<br />
//Note that MSB should be in slot 0<br />
writebuf[1] = 0x58;<br />
writebuf[0] = 0x98;<br />
//Sets up DTR to contain 0x9858 = 39,000 (ticks/rev)<br />
write_7366(DTR, writebuf);<br />
clear_reg_7366(STR);<br />
<br />
//Read periodically and send encoder value<br />
unsigned char readbuf[COUNTER_BYTES];<br />
unsigned char strbuf;<br />
char string[40];<br />
while(1) {<br />
Delayms(250); //Wait 1/4 sec.<br />
read_7366(CNTR, readbuf);<br />
sprintf(string,"Read from CNTR: %x%x\r\n", readbuf[0], readbuf[1]);<br />
putsUART2(string);<br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18374Using the LS7366R SPI Quadrature Counter2010-05-03T23:14:34Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.<br />
<br />
The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:<br />
<br />
*MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more<br />
*MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags<br />
*DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.<br />
*CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.<br />
*OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read '''the value of CNTR at the time of the last CNTR read'''.<br />
*STR- This is a status register containing status bits<br />
*IR- This register is used internally to select the register being written to or read from on the next bus transaction.<br />
<br />
Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.<br />
<br />
==Circuit==<br />
[[Image:7366_circuit1.PNG|thumb|300px|right]]<br />
<br />
The 7366 connects to the PIC using the SPI bus, as mentioned above. As seen in the circuit to the right, the relevant wires are SCK, SDI, SDO, and SS. In the code below, the SPI1 module is used, but with slight changes the SPI2 module could be used instead. There are other optional pins that could be connected for interrupts, etc., for more information see the 7366 datasheet.<br />
<br />
As seen in the diagram, the 7366 requires an external clock. Instead of using a crystal as shown, one could also supply an external clock pulse to the fCKI pin. For more information and frequency requirements, see the datasheet.<br />
<br />
==Code==<br />
The code consists of 4 files:<br />
*7366.h: A header file containing various defines<br />
*7366_lib.c: A C file containing functions that interact with the 7366<br />
*7366_decoder.c: A C file containing main() and a simple test program<br />
<br />
All three can be downloaded [[Media:7366R.zip| here]].<br />
===7366_lib.c===<br />
This file contains library functions for using the LS7366R. The most relavent functions are enable_7366(), write_7366(), and read_7366(). The configuration flags are listed in the 7366.h file. Note that the read and write functions are passed pointers to arrays to read from or write to. This uses the SPI1 module.<br />
<br />
/********************************************************************<br />
* 7366_lib.c : Library functions for using the 7366R SPI decoder<br />
*<br />
* Creation date: 16-APR-2010<br />
* Last modified: 27-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "_spi_map_tbl.h"<br />
#include "7366.h"<br />
<br />
<br />
/* enable_7366(): enables counting on 7366, sends configuration words<br />
and optionally turns on flags */<br />
void enable_7366(int config1, int flags) {<br />
unsigned char writebyte;<br />
writebyte = config1; //config word 1<br />
write_7366(MDR0,&writebyte);<br />
writebyte = flags | (4-COUNTER_BYTES); //config word 2<br />
write_7366(MDR1, &writebyte);<br />
}<br />
<br />
void disable_7366_counting() {<br />
//send disable to MDR1<br />
unsigned char writebyte;<br />
writebyte = COUNTER_BYTES;<br />
write_7366(MDR1, &writebyte);<br />
}<br />
<br />
/* write_7366(): Writes bytes in array "bytearray" to register "reg".<br />
The number of bytes written depends on the reister and mode.<br />
Config registers will write 1 byte. Other registers will<br />
write the number of bytes specified by COUNTER_BYTES */<br />
void write_7366(int reg,unsigned char *bytearray) {<br />
unsigned char ir = (0x2 << 6 ) | (reg << 3); //Instruction<br />
unsigned char ReadData;<br />
if ( (reg == MDR0) || (reg == MDR1) || (reg == STR) ) {<br />
//One byte to write<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
SpiChnPutC(SPICHN, bytearray[0]); //Clock out write byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (garbage /dont care)<br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
if ( (reg == DTR) || (reg == CNTR) || (reg == OTR) ) {<br />
//1-4 bytes to read<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
//Do reads<br />
int i;<br />
for (i=0;i<COUNTER_BYTES;i++) {<br />
SpiChnPutC(SPICHN, bytearray[i]); //Clock out byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (don't care)<br />
} <br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
}<br />
<br />
<br />
/* read_7366(): Reads bytes to array "bytearray" from register "reg".<br />
The number of bytes read depends on the reister and mode.<br />
Config registers will read 1 byte. Other registers will<br />
read the number of bytes specified by COUNTER_BYTES */<br />
void read_7366(int reg, unsigned char *bytearray) {<br />
unsigned char ir = (0x1 << 6 ) | (reg << 3); //Instruction<br />
unsigned char ReadData;<br />
if ( (reg == MDR0) || (reg == MDR1) || (reg == STR) ) {<br />
//One byte to read<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
SpiChnPutC(SPICHN, 0); //Clock out dummy byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (register)<br />
*bytearray = ReadData;<br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
if ( (reg == DTR) || (reg == CNTR) || (reg == OTR) ) {<br />
//1-4 bytes to read<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
//Do reads<br />
int i;<br />
for (i=0;i<COUNTER_BYTES;i++) {<br />
SpiChnPutC(SPICHN, 0); //Clock out dummy byte after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (register)<br />
bytearray[i] = ReadData;<br />
} <br />
SLAVE_SELECT = 1; //End comm<br />
return;<br />
}<br />
}<br />
<br />
/* clear_reg_7366(): Clears the given register */<br />
void clear_reg_7366(int reg) {<br />
char ReadData;<br />
volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];<br />
char ir = (reg << 3); //Instruction<br />
SLAVE_SELECT = 0;<br />
delay(); //Setup time<br />
SpiChnPutC(SPICHN, ir); //Write instruction after TX buffer empty<br />
while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full<br />
ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)<br />
SLAVE_SELECT = 1;<br />
}<br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
===7366_decoder.c===</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18373Using the LS7366R SPI Quadrature Counter2010-05-03T23:10:01Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.<br />
<br />
The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:<br />
<br />
*MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more<br />
*MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags<br />
*DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.<br />
*CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.<br />
*OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read '''the value of CNTR at the time of the last CNTR read'''.<br />
*STR- This is a status register containing status bits<br />
*IR- This register is used internally to select the register being written to or read from on the next bus transaction.<br />
<br />
Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.<br />
<br />
==Circuit==<br />
[[Image:7366_circuit1.PNG|thumb|300px|right]]<br />
<br />
The 7366 connects to the PIC using the SPI bus, as mentioned above. As seen in the circuit to the right, the relevant wires are SCK, SDI, SDO, and SS. In the code below, the SPI1 module is used, but with slight changes the SPI2 module could be used instead. There are other optional pins that could be connected for interrupts, etc., for more information see the 7366 datasheet.<br />
<br />
As seen in the diagram, the 7366 requires an external clock. Instead of using a crystal as shown, one could also supply an external clock pulse to the fCKI pin. For more information and frequency requirements, see the datasheet.<br />
<br />
==Code==<br />
The code consists of 4 files:<br />
*7366.h: A header file containing various defines<br />
*7366_lib.c: A C file containing functions that interact with the 7366<br />
*7366_decoder.c: A C file containing main() and a simple test program<br />
<br />
All three can be downloaded [[Media:7366R.zip| here]].</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:7366R.zip&diff=18372File:7366R.zip2010-05-03T23:09:13Z<p>Thomas Peterson: </p>
<hr />
<div></div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18371Using the LS7366R SPI Quadrature Counter2010-05-03T21:45:43Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.<br />
<br />
The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:<br />
<br />
*MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more<br />
*MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags<br />
*DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.<br />
*CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.<br />
*OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read '''the value of CNTR at the time of the last CNTR read'''.<br />
*STR- This is a status register containing status bits<br />
*IR- This register is used internally to select the register being written to or read from on the next bus transaction.<br />
<br />
Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.<br />
<br />
==Circuit==<br />
[[Image:7366_circuit1.PNG|thumb|300px|right]]<br />
<br />
The 7366 connects to the PIC using the SPI bus, as mentioned above. As seen in the circuit to the right, the relevant wires are SCK, SDI, SDO, and SS. In the code below, the SPI1 module is used, but with slight changes the SPI2 module could be used instead. There are other optional pins that could be connected for interrupts, etc., for more information see the 7366 datasheet.<br />
<br />
As seen in the diagram, the 7366 requires an external clock. Instead of using a crystal as shown, one could also supply an external clock pulse to the fCKI pin. For more information and frequency requirements, see the datasheet.<br />
<br />
==Code==<br />
The code consists of 4 files:<br />
*7366.h: A header file containing various defines<br />
*7366_lib.c: A C file containing functions that interact with the 7366<br />
*7366_decoder.c: A C file containing main() and a simple test program<br />
<br />
All three can be downloaded here.</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18370Using the LS7366R SPI Quadrature Counter2010-05-03T21:42:59Z<p>Thomas Peterson: /* Circuit */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.<br />
<br />
The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:<br />
<br />
*MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more<br />
*MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags<br />
*DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.<br />
*CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.<br />
*OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read '''the value of CNTR at the time of the last CNTR read'''.<br />
*STR- This is a status register containing status bits<br />
*IR- This register is used internally to select the register being written to or read from on the next bus transaction.<br />
<br />
Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.<br />
<br />
==Circuit==<br />
[[Image:7366_circuit1.PNG|thumb|300px|right]]<br />
<br />
The 7366 connects to the PIC using the SPI bus, as mentioned above. As seen in the circuit to the right, the relevant wires are SCK, SDI, SDO, and SS. In the code below, the SPI1 module is used, but with slight changes the SPI2 module could be used instead. There are other optional pins that could be connected for interrupts, etc., for more information see the 7366 datasheet.<br />
<br />
As seen in the diagram, the 7366 requires an external clock. Instead of using a crystal as shown, one could also supply an external clock pulse to the fCKI pin. For more information and frequency requirements, see the datasheet.<br />
<br />
==Code==</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18369Using the LS7366R SPI Quadrature Counter2010-05-03T21:42:44Z<p>Thomas Peterson: /* Circuit */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.<br />
<br />
The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:<br />
<br />
*MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more<br />
*MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags<br />
*DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.<br />
*CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.<br />
*OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read '''the value of CNTR at the time of the last CNTR read'''.<br />
*STR- This is a status register containing status bits<br />
*IR- This register is used internally to select the register being written to or read from on the next bus transaction.<br />
<br />
Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.<br />
<br />
==Circuit==<br />
[[Image:7366_circuit1.PNG|thumb|500px|right]]<br />
<br />
The 7366 connects to the PIC using the SPI bus, as mentioned above. As seen in the circuit to the right, the relevant wires are SCK, SDI, SDO, and SS. In the code below, the SPI1 module is used, but with slight changes the SPI2 module could be used instead. There are other optional pins that could be connected for interrupts, etc., for more information see the 7366 datasheet.<br />
<br />
As seen in the diagram, the 7366 requires an external clock. Instead of using a crystal as shown, one could also supply an external clock pulse to the fCKI pin. For more information and frequency requirements, see the datasheet.<br />
<br />
==Code==</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:7366_circuit1.PNG&diff=18368File:7366 circuit1.PNG2010-05-03T21:39:25Z<p>Thomas Peterson: Circuit for the 7366 counter</p>
<hr />
<div>Circuit for the 7366 counter</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18363Using the LS7366R SPI Quadrature Counter2010-05-02T22:49:41Z<p>Thomas Peterson: /* Overview of the LS7366R */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.<br />
<br />
The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:<br />
<br />
*MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more<br />
*MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags<br />
*DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.<br />
*CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.<br />
*OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read '''the value of CNTR at the time of the last CNTR read'''.<br />
*STR- This is a status register containing status bits<br />
*IR- This register is used internally to select the register being written to or read from on the next bus transaction.<br />
<br />
Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.<br />
<br />
==Circuit==<br />
<br />
==Code==</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:7166ckt_png.PNG&diff=18349File:7166ckt png.PNG2010-04-30T04:54:35Z<p>Thomas Peterson: uploaded a new version of "Image:7166ckt png.PNG"</p>
<hr />
<div>Circuit for LS7166</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:7166ckt_png.PNG&diff=18348File:7166ckt png.PNG2010-04-30T04:53:23Z<p>Thomas Peterson: uploaded a new version of "Image:7166ckt png.PNG"</p>
<hr />
<div>Circuit for LS7166</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18347Using the LS7166 Quadrature Counter2010-04-30T04:51:31Z<p>Thomas Peterson: /* Overview of the LS7166 */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
A chip with similar capability is the [[Using the LS7366R SPI Quadrature Counter| LS7366R]], which uses the SPI bus to communicate instead of in parallel, is discussed on separate wiki page.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also serve as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example the optional control bit /ABGT is tied to ground..<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. The value of the PR register is loaded whenever the counter borrows (downticks from 0). Note that the code does '''not''' reset to 0 when it reaches PR. To find the actual position, the software will have to take count MOD PR.<br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
===7166_test.c===<br />
This c file simply loops continuously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}<br />
<br />
==Shortfalls / Next Steps==<br />
The biggest problem with the system above is that in divide-by-n mode the chip does not reset the counter to zero when CNTR = PR. Because of this, if running the motor backwards continuously (counting down), the value of CNTR will always be between 0 and PR at the current position. However, when running forward (counting up) the value of CNTR goes past PR. Because of this, the pic software must be written so that the read value of CNTR is modded by the maximum count number to find the position. For instance, if the maximum count is 4000, and CNTR is 4200, then the actual position is 200 (out of 4000). Eventually the CNTR will overflow if not reduced. In this case, the /CY flag can be set to interrupt the PIC so that the correct value of CNTR can be reloaded. Alternatively, at each read the PIC could send back the actual count value. Note that this would require writing to PR and then doing a PR->CNTR transfer using the control register.<br />
<br />
If possible, the 7133R may be a better choice since it does bounded counting correctly.</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7366R_SPI_Quadrature_Counter&diff=18346Using the LS7366R SPI Quadrature Counter2010-04-30T00:51:07Z<p>Thomas Peterson: New page: ==Introduction== The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pul...</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7366R.pdf LS7366R] is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the [[Using_the_LS7166_Quadrature_Counter|LS7166]], which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses.<br />
The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.<br />
<br />
The code on this page is for the PIC32.<br />
<br />
==Overview of the LS7366R==<br />
<br />
==Circuit==<br />
<br />
==Code==</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18345Using the LS7166 Quadrature Counter2010-04-30T00:45:32Z<p>Thomas Peterson: /* Introduction */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
A chip with similar capability is the [[Using the LS7366R SPI Quadrature Counter| LS7366R]], which uses the SPI bus to communicate instead of in parallel, is discussed on separate wiki page.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example the optional control bit /ABGT is tied to ground..<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. The value of the PR register is loaded whenever the counter borrows (downticks from 0). Note that the code does '''not''' reset to 0 when it reaches PR. To find the actual position, the software will have to take count MOD PR.<br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
===7166_test.c===<br />
This c file simply loops continuously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}<br />
<br />
==Shortfalls / Next Steps==<br />
The biggest problem with the system above is that in divide-by-n mode the chip does not reset the counter to zero when CNTR = PR. Because of this, if running the motor backwards continuously (counting down), the value of CNTR will always be between 0 and PR at the current position. However, when running forward (counting up) the value of CNTR goes past PR. Because of this, the pic software must be written so that the read value of CNTR is modded by the maximum count number to find the position. For instance, if the maximum count is 4000, and CNTR is 4200, then the actual position is 200 (out of 4000). Eventually the CNTR will overflow if not reduced. In this case, the /CY flag can be set to interrupt the PIC so that the correct value of CNTR can be reloaded. Alternatively, at each read the PIC could send back the actual count value. Note that this would require writing to PR and then doing a PR->CNTR transfer using the control register.<br />
<br />
If possible, the 7133R may be a better choice since it does bounded counting correctly.</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18344Using the LS7166 Quadrature Counter2010-04-30T00:40:03Z<p>Thomas Peterson: </p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example the optional control bit /ABGT is tied to ground..<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. The value of the PR register is loaded whenever the counter borrows (downticks from 0). Note that the code does '''not''' reset to 0 when it reaches PR. To find the actual position, the software will have to take count MOD PR.<br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
===7166_test.c===<br />
This c file simply loops continuously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}<br />
<br />
==Shortfalls / Next Steps==<br />
The biggest problem with the system above is that in divide-by-n mode the chip does not reset the counter to zero when CNTR = PR. Because of this, if running the motor backwards continuously (counting down), the value of CNTR will always be between 0 and PR at the current position. However, when running forward (counting up) the value of CNTR goes past PR. Because of this, the pic software must be written so that the read value of CNTR is modded by the maximum count number to find the position. For instance, if the maximum count is 4000, and CNTR is 4200, then the actual position is 200 (out of 4000). Eventually the CNTR will overflow if not reduced. In this case, the /CY flag can be set to interrupt the PIC so that the correct value of CNTR can be reloaded. Alternatively, at each read the PIC could send back the actual count value. Note that this would require writing to PR and then doing a PR->CNTR transfer using the control register.<br />
<br />
If possible, the 7133R may be a better choice since it does bounded counting correctly.</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18343Using the LS7166 Quadrature Counter2010-04-30T00:33:20Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. The value of the PR register is loaded whenever the counter borrows (downticks from 0). Note that the code does '''not''' reset to 0 when it reaches PR. To find the actual position, the software will have to take count MOD PR.<br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
===7166_test.c===<br />
This c file simply loops continuously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Microchip_PICs&diff=18321Microchip PICs2010-04-22T14:00:42Z<p>Thomas Peterson: /* Other PIC Resources */</p>
<hr />
<div>'''Note: Most code on this page was written using the CCS compiler. Microchip's MPLAB is also occasionally used.'''<br />
<br />
<b>[http://peshkin.mech.northwestern.edu/pic/code Link to all sample code here.]</b><br />
*[[Sample code for most PIC18F4520 operations]]<br />
*[http://www.ccsinfo.com/forum/ CCS user forum]<br />
<br><br />
__TOC__<br />
== Using the PIC18F4520 ==<br />
*[[4520 Board intro]]<br />
*[[4520 Board construction]]<br />
*[[4520 Board use]]<br />
*[[Microcontroller PIC18F4520]]<br />
*[[PIC MCUs: Capabilities of PIC18F4520]]<br />
**[[PIC18F4520: PWM Motor Control]]<br />
**[[PIC18F4520: Digital Outputs]]<br />
**[[PIC18F4520: Digital Inputs]]<br />
**[[PIC18F4520: Serial Digital-to-Analog Conversion]]<br />
**[[PIC18F4520: Analog Inputs]]<br />
**[[PIC18F4520: Timers]]<br />
**[[PIC18F4520: Comparator]]<br />
<br />
== Example Code in C for PIC18F4520==<br />
*[[C Example: Parallel Interfacing with LCDs]]<br />
*[[C Example: Digital Inputs]]<br />
*[[C Example: PWM Motor Control]]<br />
*[[C Example: Comparators]]<br />
*[[C Example: Digital Outputs]]<br />
*[[C Example: Analog Inputs]]<br />
*[[C Example: Digital Outputs (Ports)]]<br />
*[[C Example: Bi-Directional PWM Motor Control]]<br />
*[[C Example: Serial LCD]]<br />
<br />
== Using the PIC32MX Series ==<br />
*[[Introduction to the PIC32]]<br />
*[[Getting Started with PIC32]] (includes NU32 development board)<br />
** [[ME 333 Lab 2]] - Includes Digital IO, Analog Input, Parallel LCD and RS232 Com<br />
** [[ME 333 Lab 4]] - Includes PWM and Encoder Motor Control<br />
*[[Microcontroller PIC32MX460F512L]]<br />
*[[Programming HID Bootloader on PIC32]] <br />
*[[HelloWorld PIC32|Directions to code HelloWorld]]<br />
*[[Directions to Load Files to PIC32 with HID Bootloader]]<br />
<br />
<br />
*[[PIC MCUs: Capabilities of PIC32MX]]<br />
**[[Programming HID Bootloader on PIC32 |PIC32MX: Bootloader]]<br />
**Simple Programs<br />
***[[PIC32MX: Digital Inputs]]<br />
***[[PIC32MX: Digital Outputs]]<br />
***[[PIC32MX: Analog Inputs]]<br />
**Communication<br />
***[[PIC32MX: Parallel LCD]]<br />
***[[PIC32MX: RS232]]<br />
***[[PIC32MX: USB Communication with a PC]]<br />
***[[PIC32MX: SPI External RAM]]<br />
***[[PIC32MX: I2C DAC]]<br />
***[[PIC32MX: I2C EEPROM]]<br />
***[[PIC32MX: SPI EEPROM]]<br />
***[[PIC32MX: I2C Communication between PIC32s]]<br />
***[[PIC32MX: SPI Communication between PIC32s]]<br />
**Motor Control<br />
***[[PIC32MX: PWM Motor Control|PIC32MX: PWM Motor Control]]<br />
***[[PIC32MX: Encoder Motor Control|PIC32MX: Encoder Motor Control]]<br />
***[[PIC32MX: Servo Control|PIC32MX: Servo Control]]<br />
***[[PIC32MX: Driving a Stepper Motor]]<br />
**Capabilities<br />
***[[PIC32MX: Benchmarking Mathematical Operations]]<br />
***[[PIC32MX: FFT of Analog Input]]<br />
***[[PIC32MX: Inverse FFT]]<br />
***[[PIC32MX: Sinusoidal Analog Output]]<br />
***[[PIC32MX: XBee Wireless Round-trip Latency]]<br />
***[[PIC32MX: Interfacing with Force Sensors from a Scale]]<br />
<br />
==Other PIC Resources ==<br />
<br />
*[[PIC PWM Motor Driver]]<br />
*[[Writing Code with the C18 Compiler]]<br />
*[[Controlling a seven segment display]]<br />
*[[PIC Microcontrollers with C18 Compiler]]<br />
*[[PIC Motor Control and Serial Port Example]]<br />
*[[PIC16F684]]<br />
*[[PIC Microcontrollers with CCS Compiler]]<br />
*[[Stepper motor control with the PIC]]<br />
*[[PIC/C18 Compiler Tips and Troubleshooting]]<br />
*[[CCS C]]<br />
*[[Analog Input]]<br />
*[[PIC MCUs: Hardware and Connections]]<br />
*[[Storing constant data in program memory]]<br />
*[[PIC Analog-Digital-Converter Example]]<br />
*[[PIC Microcontroller]]<br />
*[[Digital inputs & outputs]]<br />
*[[Debugging C on a PIC]]<br />
*[[Analog Output]]<br />
*[[PIC MCUs: Software]]<br />
*[[Interfacing to External EEPROM]]<br />
*[[CCS IDE]]<br />
*[[Interrupts]]<br />
*[[PIC16F684 Registers]]<br />
*[[Waveform Generation with AD9833]]<br />
*[[Waveform Generation with AD9833, and SPI]]<br />
*[[PIC computation time benchmarks]]<br />
*[[PICkit 1]]<br />
*[[PIC MCUs: 4520 Board]]<br />
*[[More debugging tips]]<br />
*[[Example Writeup: Analog Input]]<br />
*[[LED Drivers]]<br />
*[[Embedded Programming Tips for CCS C]]<br />
*[[Wireless PIC bootloading]]<br />
*[[PIC 18f4553]]<br />
*[[I2C Motor Controller]]<br />
*[[Driving a piezo speaker with a PIC]]<br />
*[[PIC Servo Controller]]<br />
*[[Watchdog timer]]<br />
*[[Interfacing PIC with SPI memory]]<br />
*[[Using the LS7166 Quadrature Counter]]<br />
<br />
==Programming the PIC with Microchip's MPLAB==</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18320Using the LS7166 Quadrature Counter2010-04-22T06:16:46Z<p>Thomas Peterson: /* 7166_lib.c */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
'''NOTE: this code still has a slight bug: at overflow the CNTR doesn't roll to 0. This will be fixed soon'''<br />
<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. <br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
===7166_test.c===<br />
This c file simply loops continously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18319Using the LS7166 Quadrature Counter2010-04-22T06:16:38Z<p>Thomas Peterson: /* 7166_test.c */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
'''NOTE: this code still has a slight bug: at overflow the CNTR doesn't roll to 0. This will be fixed soon'''<br />
<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. <br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 22-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
<br />
===7166_test.c===<br />
This c file simply loops continously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 21-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Sensors&diff=18318Sensors2010-04-22T06:16:15Z<p>Thomas Peterson: </p>
<hr />
<div>*[[Rotary Encoder]]<br />
*[[Strain Gauge]]<br />
*[[Photodiodes and Phototransistors]]<br />
*[[Hall Effect Sensor]]<br />
*[[Optointerrupter]]<br />
*[[Reed Switch]]<br />
*[[Sensing optical tape]]<br />
*[[Optoreflector]]<br />
*[[Limit Switch]]<br />
*[[Photocell]]<br />
*[[Thermistor]]<br />
*[[Microphones]]<br />
*[[Accelerometers]]<br />
*[[Ambient light color sensing]]<br />
*[[Adding a magnetic encoder to a GM3 Gearmotor]]<br />
*[[Global Positioning System]]<br />
*[[Adding an Encoder to Your Gearmotor]]<br />
*[[Quadrature decoding in hardware, or just counters]]<br />
*[[Quadrature decoding in software]]<br />
*[[Ultrasonic ranging]]<br />
*[[Reading RFID tags]]<br />
*[[Phase-Sensitive Detection]]<br />
*[[Lateral-Effect Photodiode]]<br />
*[[Optical Locating]]<br />
*[[Opto-isolators]]<br />
*[[Fingertip laser light sensor]]<br />
*[[Interfacing with a touchscreen]]<br />
*[[Interfacing with a mouse]]<br />
*[[Pressure Sensing]]<br />
*[[Resistive Touchscreen]]<br />
*[[Parallax GPS (Global Positioning System) Receiver Module]]<br />
*[[Potentiometers]]<br />
*[[Switch Debouncing]]<br />
*[[Interfacing with a Photodiode Array]]<br />
*[[Optics]]<br />
*[[Using a laser]]<br />
*[[Audio recording and playback]]<br />
*[[IR Target Illumination]]<br />
*[[Using the LS7166 Quadrature Counter]]</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18317Using the LS7166 Quadrature Counter2010-04-22T06:07:50Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
'''NOTE: this code still has a slight bug: at overflow the CNTR doesn't roll to 0. This will be fixed soon'''<br />
<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. <br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 22-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
<br />
===7166_test.c===<br />
This c file simply loops continously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 20-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18316Using the LS7166 Quadrature Counter2010-04-22T06:06:29Z<p>Thomas Peterson: /* 7166_test.c */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. <br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 22-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
<br />
===7166_test.c===<br />
This c file simply loops continously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 20-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18315Using the LS7166 Quadrature Counter2010-04-22T06:06:06Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[Media:7166.zip | this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. <br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 22-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
<br />
===7166_test.c===<br />
This c file simply loops continously and reads values from the LS7166. Not that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 20-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18314Using the LS7166 Quadrature Counter2010-04-22T06:05:12Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==<br />
The code consists of three files:<br />
*7166.h - This header file contains function definitions, pin defines, etc.<br />
*7166_lib.c - This c file contains functions that interface with the 7166<br />
*7166_test.c - This c file contains the main function and is a simple example of how to use the library functions<br />
All three can be download from [[7166.zip|this zip file]].<br />
===7166_lib.c===<br />
This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. <br />
<br />
The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.<br />
<br />
/********************************************************************<br />
* 7166_lib.c : Library functions for using the 7166R SPI decoder<br />
*<br />
* Creation date: 20-APR-2010<br />
* Last modified: 22-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include <plib.h><br />
#include <peripheral/generic.h><br />
#include <peripheral/spi.h><br />
<br />
#include "7166.h"<br />
<br />
/* Comm_dir: Switches tris bits on comm bus to inputs or outputs<br />
If the bus is changed from port B to other pins, this fcn<br />
will need to be changed */<br />
void comm_dir(int dir) {<br />
if (dir==IN) {<br />
//Set as input<br />
TRISB |= 0x00FF; <br />
} <br />
else {<br />
//set as output<br />
TRISB &= 0xFF00; <br />
} <br />
} <br />
<br />
/* bus_out : This function sends out a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
void bus_out(unsigned char byte) {<br />
comm_dir(OUT);<br />
LATB = byte;<br />
} <br />
<br />
/* bus_in : This function reads a byte on the data bus<br />
If the bus pins are changed, this fcn will need<br />
to be changed too. <br />
This does NOT handle other control lines. */<br />
unsigned char bus_in() {<br />
comm_dir(IN);<br />
return (unsigned char)(PORTB & 0x00FF);<br />
} <br />
<br />
void init_7166() {<br />
write_ctrl(MCR,0x20); //Performs master reset<br />
write_ctrl(MCR,0x04); //Sub-reset<br />
write_ctrl(ICR,0x18); //Enables A/B, sets up pin 3-4<br />
write_ctrl(OCCR, 0x34); //Divide by n mode 0x04<br />
write_ctrl(QR,0x03); //x4 quadrature mode 0x04<br />
//0x3840->Pr<br />
write_PR(0x3840); //Upper limit<br />
} <br />
<br />
/* write_ctrl: writes a byte to the given control register */<br />
void write_ctrl(unsigned char reg, unsigned char byte){<br />
NCS = 0; //Select chip<br />
NRD = 1;<br />
C_ND = 1; //Setup to write to control register<br />
unsigned char op = (reg << 6) | byte;<br />
bus_out(op);<br />
latchWR(); //write value<br />
NCS = 1; //Unselect chip <br />
} <br />
<br />
void latchWR() {<br />
delay(); //Setup time (???)<br />
NWR = 0; //Latch out write<br />
delay(); //Hold time (???)<br />
NWR = 1; //End write <br />
} <br />
<br />
void write_PR(unsigned int value) {<br />
unsigned int write_value;<br />
//Prepare for PR read<br />
write_ctrl(MCR,0x01); //Reset ctr<br />
NCS = 0; //select chip<br />
C_ND = 0; //Writes will be to PR _only_<br />
write_value = value & 0xff; //Get lowest byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 8) & 0xff; //Get next byte<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
write_value = (value >> 16) & 0xff; //Get MSB<br />
bus_out((unsigned char)write_value);<br />
latchWR(); //Latch out value<br />
NCS = 1; //Unselect chip<br />
write_ctrl(MCR,0x08); //PR->CNTR<br />
}<br />
<br />
unsigned int read_reg(unsigned char reg) {<br />
unsigned int read_value;<br />
if (reg == OSR) {<br />
NCS = 0; //Select chip<br />
C_ND = 1;<br />
NWR = 1;<br />
read_value = latchRD();<br />
NCS = 1; //Deselect chip<br />
return read_value; <br />
} <br />
if (reg == OL) {<br />
write_ctrl(MCR,0x03); //Prepare for read<br />
C_ND = 0;<br />
NRD = 1;<br />
NWR = 1;<br />
NCS = 0; //Select chip<br />
unsigned char read_byte;<br />
read_byte = latchRD(); //Read byte 0<br />
read_value = read_byte;<br />
delay();<br />
read_byte = latchRD(); //REad byte 1<br />
int tmp = read_byte;<br />
tmp = tmp << 8;<br />
read_value |= tmp;<br />
delay();<br />
read_byte = latchRD(); //Read byte 3<br />
tmp = read_byte;<br />
tmp = tmp << 16;<br />
read_value |= tmp;<br />
NCS = 1; //Unselect chip<br />
return read_value;<br />
}<br />
//no other regs can be read <br />
} <br />
<br />
unsigned char latchRD() {<br />
NRD = 0; //Read<br />
delay(); //???<br />
unsigned char read_byte = bus_in();<br />
delay();<br />
NRD = 1; //End latch<br />
return read_byte;<br />
} <br />
<br />
/* Delays for 10 cycles, plus branch time */<br />
void delay() {<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
asm("nop");<br />
}<br />
<br />
<br />
===7166_test.c===<br />
This c file simply loops continously and reads values from the LS7166. Not that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.<br />
<br />
/***********************************************************<br />
* 7366_test.c : C-file which runs tests that use the<br />
* LS7166 parallel-interface encoder chip.<br />
* <br />
* Creation date: 20-APR-2010<br />
* Last modified: 20-APR-2010<br />
*<br />
* Thomas Peterson<br />
* LIMS<br />
********************************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include "7166.h"<br />
#include <plib.h><br />
<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
#define FPB 80000000<br />
<br />
void setup_rs232(int pbClk) {<br />
#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN<br />
#define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR<br />
// Open UART2 with config1 and config2<br />
OpenUART2( config1, config2, pbClk/16/9600-1); // calculate actual BAUD generate value.<br />
putsUART2("UART init done\r\n");<br />
}<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < FPB/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Init I/O<br />
//Set comm bits as outputs<br />
TRISD &= 0xFFE1; //D4-D1 outputs (0)<br />
TRISC &= 0xFFE1; //C4-C1 outputs (0)<br />
//TRISB (data bus) handled by comm fcns<br />
//Set outputs high (inactive)<br />
NWR = 1;<br />
NCS = 1;<br />
C_ND = 1;<br />
NRD = 1;<br />
<br />
<br />
//UART setup<br />
setup_rs232(pbClk);<br />
<br />
//7166 setup<br />
init_7166();<br />
<br />
//Read peroidically and send encoder value<br />
unsigned int readval;<br />
char string[40];<br />
while(1) {<br />
Delayms(250);<br />
readval = read_reg(OL);<br />
sprintf(string,"Read from OL: %x , OSR \r\n", readval );<br />
putsUART2(string);<br />
} <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:7166.zip&diff=18313File:7166.zip2010-04-22T05:53:48Z<p>Thomas Peterson: Code for LS7166, V1 (some slight bugs)</p>
<hr />
<div>Code for LS7166, V1 (some slight bugs)</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18312Using the LS7166 Quadrature Counter2010-04-22T05:51:10Z<p>Thomas Peterson: /* Circuit */</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
[[Image:7166ckt_png.PNG|thumb|500px|right]]<br />
<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:7166ckt_png.PNG&diff=18311File:7166ckt png.PNG2010-04-22T05:49:52Z<p>Thomas Peterson: Circuit for LS7166</p>
<hr />
<div>Circuit for LS7166</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18310Using the LS7166 Quadrature Counter2010-04-22T05:28:33Z<p>Thomas Peterson: </p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.<br />
<br />
Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example, the optional control bit /ABGT is tied to ground, and the optional control bit /LCTR is tied to the flag /CY.<br />
<br />
As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.<br />
<br />
==Code==</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Using_the_LS7166_Quadrature_Counter&diff=18309Using the LS7166 Quadrature Counter2010-04-22T05:16:03Z<p>Thomas Peterson: New page: ==Introduction== The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, wh...</p>
<hr />
<div>==Introduction==<br />
The [http://www.lsicsi.com/pdfs/Data_Sheets/LS7166.pdf LS7166] is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.<br />
<br />
In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.<br />
<br />
==Overview of the LS7166==<br />
The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also server as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.<br />
<br />
The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at '''high''' at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).<br />
<br />
The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.<br />
*MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)<br />
*CNTR- Counter. This register contains the current count value. '''CNTR cannot be read to or written from directly'''. Instead PR or OR must be used.<br />
*PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)<br />
*ICR- Input control register. This register sets up input writing modes. (Write only)<br />
*OSR- Output status register. This register contains some status bits. (Read only)<br />
*OCCR- Output control register. This register sets up output modes. (Write only)<br />
*QR- Quadrature register. This register sets up the quadrature mode. (Write only)<br />
*OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)<br />
<br />
==Circuit==<br />
asdf</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=18051Music from the Heart -- Music Suit2010-03-19T06:21:07Z<p>Thomas Peterson: /* Reflections */ minor fixes</p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the user's heart beat.<br />
<br />
For a video demonstration, click [http://www.youtube.com/watch?v=YyipByy7m6I here].<br />
<br />
{{multiple image<br />
| width1 = 150<br />
| width2 = 269<br />
| align = center<br />
| header = Music from the Heart Project<br />
| image1 = Music from the heart overview.jpg<br />
| caption1 = The full "music suit" on James, with heart rate sensor on his finger. <br />
| image2 = Drum overview mfth.jpg<br />
| caption2 = A closer view of the drum mechanism<br />
}}<br />
<br />
<br clear=all><br />
<br />
==Team Members==<br />
<br />
[[Image:Mfth team picture.JPG|thumb|200px|From left: Thomas, Eric, James]]<br />
<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user for [http://en.wikipedia.org/wiki/Electrocardiography ECG] would simplify the process of identifying a heart beat, but attaching these sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger-tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High-Pass Filter]]<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]]: gain = -470<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low-pass filter]: cutoff frequency = 4.82Hz, gain = -70<br />
# [http://www.electronics-tutorials.ws/filter/filter_7.html Band-pass filter]: Frequency range = 0.72 to 3.39Hz, gain = -46.8<br />
# [[Comparators|Comparator]]: output to the PIC pin A14, with pull-up resistor to 3.3V and capacitors to smooth out the signal.<br />
<br />
The complete signal processing circuit is shown below. Note the potentiometers, which can be adjusted for some users if the output signal is not clear.<br />
<br />
<br />
<br />
[[Image:Heartrate monitor.PNG|thumb|center|900px|Heart rate monitor circuit diagram. Click for larger image.]]<br />
<br />
<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
[[Image:Heart rate sensor mfth.JPG|thumb|200px|Heart rate sensor with the latex band pulled away so that the emitter-detector is revealed]]<br />
The sensor itself was a QRB1114 emitter-detector pair that was encased in open-cell foam insulation that served as the finger-holder. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, and 100 + 75 = 175ms of drive time, the fastest heartbeat our drum could play was 96 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the analog signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency (see [[PIC32MX: FFT of Analog Input]] for more information). Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time, it was enough to make the drum beating noticeably erratic. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak-detection algorithms, but they seemed too complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L298N.pdf|L298 H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
[[Image:T21_motorckt_png.PNG|thumb|center|600px|Motor Circuit (non-motor PIC connections not shown). Click for larger image.]]<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
<br />
[[Image:Drum stick mount mfth.jpg|thumb|200px|Attachment for the drumstick to the motor shaft. Split-clamp to the motor shaft on the left, drum stick on the right|right]]<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a drumset small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.75"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snugly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: a hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
<br />
<br />
[[Image:Motor mount mfth.jpg|thumb|200px|Mounting bracket for the motor-stick assembly.|right]]<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum and holes in the face of the motor. A sheet metal bracket with holes in the appropriate places attached the motor to two of the tuning screws. The bracket was made by cutting and bending a piece of 16 gauge steel sheet metal to the appropriate size, with a contour fitting the curvature of the drum, and an L-shaped portion to attach the motor above the drum head. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking directly on the head of the drum, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.75"x2" (aluminum would work just as well, this is what was available) <br />
*16 gauge steel sheet metal for bracket<br />
*2 pop rivets<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
{{multiple image<br />
| align = center<br />
| width = 500<br />
| image1 = T21_musicckt_png.PNG<br />
| caption1 = Music circuit diagram. The summing amplifier is not shown.<br />
| image2 = Solder board mfth.jpg<br />
| alt2 = Red cartouche<br />
| caption2 = Music chips are in the center of the board. H-bridge for motor driving is on the far right, along with the power supply plug. The left side is an unsuccessful attempt at moving the heart rate circuitry to the solder board<br />
}}<br />
<br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, allowing no current to pass through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
All the tilt switches experienced unwanted inertial effects. As they are mechanical systems, every movement, whether tilting or simply bumping the switches, could bounce the ball around inside the switch casing. This caused the switch output voltage to oscillate in response to unsmooth movements, affecting our music tone generation.<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the most reliable and sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
<br />
[[Image:Wrist sensor mfth.JPG|thumb|200px|Wrist tilt sensor. Ankles are similar, with a larger strap.]]<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (leftover scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
<br />
[[Image:Shoulder sensors mfth.JPG|thumb|200px|Shoulder straps containing the tilt sensors.]]<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move.<br />
<br />
=Code=<br />
To download the entire C file, click [[Media:Main_music.c|here]]. Below, the file is split into sections for comments.<br />
<br />
/**********************************************************<br />
* main_music.c: Main c file for "Music Suit" project<br />
* <br />
* Thomas Peterson, James Rein, Eric West<br />
* ME333 Winter 2010<br />
**********************************************************/<br />
<br />
#include <HardwareProfile.h><br />
#include <delays.c> //Delayms and Delayus functions<br />
<br />
The first section defines all the input and output pins used with the motor, sensor, and music circuits.<br />
<br />
////////////////////////////////////////////////////<br />
//Pin Defines<br />
<br />
//YMZ Chip selects<br />
#define CS1 LATCbits.LATC2<br />
#define CS2 LATCbits.LATC3<br />
#define CS3 LATCbits.LATC4<br />
#define CS4 LATGbits.LATG6<br />
//YMZ communication pins<br />
#define NWR LATBbits.LATB0 //"Not WRite": active low write line<br />
#define A0 LATBbits.LATB6 //Address 0<br />
#define NIC LATCbits.LATC1 //"Not Input Clear": active low reset line<br />
//YMZ data pins<br />
#define D0 LATGbits.LATG9<br />
#define D1 LATEbits.LATE8<br />
#define D2 LATEbits.LATE9<br />
#define D3 LATBbits.LATB5<br />
#define D4 LATBbits.LATB4<br />
#define D5 LATBbits.LATB3<br />
#define D6 LATBbits.LATB2<br />
#define D7 LATBbits.LATB1<br />
<br />
//Tilt inputs<br />
#define ENC PORTDbits.RD3<br />
#define END PORTDbits.RD2<br />
#define ENE PORTDbits.RD1<br />
#define ENG PORTCbits.RC14<br />
#define ENA PORTCbits.RC13<br />
#define ENC5 PORTDbits.RD0<br />
<br />
//Motor outputs<br />
#define IN1 LATBbits.LATB7<br />
#define IN2 LATAbits.LATA9<br />
#define EN LATAbits.LATA10<br />
<br />
///////////////////////////////////////<br />
//Other defines<br />
//Motor times<br />
#define MS(x) x*312.5<br />
#define DRIVETIME MS(100)<br />
#define BACKTIME MS(75)<br />
#define WAITTIME MS(150)<br />
//Channels<br />
#define CHNA 1<br />
#define CHNB 2<br />
#define CHNC 4<br />
<br />
<br />
//////////////////////////////////////////<br />
//Function definitions<br />
void write_chip(int address, int data);<br />
void portout(int byte);<br />
void channelSw(int chip, int chn, int *mixer, int state);<br />
//Global variables<br />
int mixer1, mixer2;<br />
int motorflag = 0;<br />
The initialization function sets up various parts of the system.<br />
/* init() : This function runs initializations, setting up the pic and music chips */<br />
void init() {<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
/////////////////////////////////////////////<br />
//Setup inputs / outputs<br />
AD1PCFG = 0xFFFF; //All digital inputs<br />
TRISG &= 0xFDBF; //sets G9,G6 to 0 (output); all else remains the same<br />
TRISC &= 0xFFE1; //sets C4,C3,C2,C1 to 0s (outputs); all else remains the same<br />
TRISB &= 0xFF80; //sets B0-B6 0 (output)<br />
TRISE &= 0xFCFF; //sets E8,9 to output<br />
TRISB |= 0xF000; //sets B12,B13,B14,B15 to 1s (inputs); all else remains the same<br />
TRISC |= 0x6000; //sets C14,13 to inputs<br />
TRISD |= 0x000F; //Sets D0,D1,D2,D3 to inputs<br />
TRISDbits.TRISD4 = 0; //Output for clock<br />
//Motor outputs<br />
TRISAbits.TRISA9 = 0;<br />
TRISAbits.TRISA10 = 0;<br />
TRISBbits.TRISB7 = 0;<br />
//Pulse inputs <br />
TRISAbits.TRISA14 = 1;<br />
TRISAbits.TRISA15 = 1;<br />
//ODC setup<br />
ODCCbits.ODCC1 = 1; //open drain control; can only float high (when set high) or pull low <br />
<br />
/* Initialize outputs */<br />
//YMZs<br />
CS1 = 1;<br />
CS2 = 1;<br />
CS3 = 1;<br />
CS4 = 1;<br />
NWR = 1;<br />
A0 = 0;<br />
NIC = 1;<br />
//Motor<br />
IN2 = 0;<br />
IN1 = 0;<br />
EN = 0;<br />
<br />
Delayms(100); //wait for power supply to ramp up<br />
NIC = 0; //resets YMZ284s<br />
Delayms(10);<br />
NIC = 1;<br />
<br />
In the init() function, the PIC writes all the setup data to the music chips. This includes the frequency registers, which determine the frequency of each output, the mixer, which determines which channels are on or off (1 = off), and the volume registers, which determine the volume of each channel. After the setup, the only register that is changed in normal operation is the mixer register, which merely turns channels on and off.<br />
//////////////////////////////////////////////////<br />
//Setup music chips<br />
//Setup Chip 1//<br />
CS1 = 0;<br />
write_chip(0x00,0xC7); //a low // C4<br />
write_chip(0x01,0x01); //a high<br />
write_chip(0x02,0x98); //b low // D<br />
write_chip(0x03,0x01); //b high<br />
write_chip(0x04,0x6E); //c low<br />
write_chip(0x05,0x01); //c high // E<br />
write_chip(0x07,0xFF); //mixer (0 enables)<br />
write_chip(0x08, 0x0A); //level (volume) A<br />
write_chip(0x09, 0x0A); //level (volume) B<br />
write_chip(0x0A, 0x0A); //level (volume) C<br />
CS1=1;<br />
//Setup Chip 2//<br />
CS2 = 0;<br />
write_chip(0x00,0x2C); //a low //G 392<br />
write_chip(0x01,0x01); //a high<br />
write_chip(0x02,0x0D); //b low //A 440<br />
write_chip(0x03,0x01); //b high<br />
write_chip(0x04,0xE3); //c low //C5 523.25<br />
write_chip(0x05,0x00); //c high <br />
write_chip(0x07,0xFF); //mixer (0 enables)<br />
write_chip(0x08, 0x0A); //level (volume) A<br />
write_chip(0x09, 0x0A); //level (volume) B<br />
write_chip(0x0A, 0x0A); //level (volume) C<br />
CS2=1;<br />
<br />
mixer1=0xFF;<br />
mixer2=0xFF;<br />
OC5 is used to create a PWM signal with 50% duty at 4Mhz, in effect creating a 4Mhz square wave. This is used by the music chips as a master clock.<br />
//////////////////////////////////////////////<br />
//Setup OC to create master clock for music chips (~4 Mhz)<br />
OpenOC5( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0);<br />
OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT, 20);<br />
SetDCOC5PWM(10);<br />
The interrupts are used, as mentioned above, to control when the motor is driven. The motor should drive once every rising edge.<br />
/////////////////////////////////////////////<br />
//External interrupt setups (for motor) - INT3 (A14)<br />
mINT3ClearIntFlag();<br />
mINT3IntEnable(1);<br />
mINT3SetIntPriority(6);<br />
mINT3SetEdgeMode(1); //Rising edge<br />
/////////////////////////////////////////////<br />
//External interrupt setups (for motor) - INT4 (A15)<br />
mINT4ClearIntFlag();<br />
mINT4IntEnable(1);<br />
mINT4SetIntPriority(5);<br />
mINT4SetEdgeMode(0); //Falling edge<br />
INTEnableSystemMultiVectoredInt();<br />
<br />
/////////////////////////////////////////////<br />
//Setup motor timer (used for timing delays)<br />
OpenTimer3(T3_ON | T3_PS_1_256 | T3_SOURCE_INT, 0xFFFF);<br />
ConfigIntTimer3(T3_INT_OFF);<br />
}<br />
<br />
Main function<br />
/* Main function */<br />
int main()<br />
{<br />
init(); /* Run initialization functions */<br />
Delayms(1);<br />
<br />
/* Main while loop: loop indefinietly */<br />
while (1) {<br />
In the main while loop, the PIC checks every cycle to see if an input has changed since the last cycle. Since the "mixer" variables store the current state, if an input does not match up with its valid mixer bit then it has changed recently. In this case, the channelSw() function is called to switch on or off the music channel controlled by that bit.<br />
//Check for state changes in music notes<br />
if (ENC != (mixer1 & 0x01)) { channelSw(1,CHNA,&mixer1,!ENC); } //If input ENC (enable C) is not equal to the state of channel A1 (note c), change the state<br />
if (END != (mixer1 & 0x02)) { channelSw(1,CHNB,&mixer1,!END); }<br />
if (ENE != (mixer1 & 0x04)) { channelSw(1,CHNC,&mixer1,!ENE); }<br />
if (ENG != (mixer2 & 0x01)) { channelSw(2,CHNA,&mixer2,!ENG); }<br />
if (ENA != (mixer2 & 0x02)) { channelSw(2,CHNB,&mixer2,!ENA); }<br />
if (ENC5 != (mixer2 & 0x04)) { channelSw(2,CHNC,&mixer2,!ENC5); }<br />
During the main while loop, the motor flag variable is checked to see if the motor should be driven. If so, it enters a sequence where it uses timer3 to determine the current stage in the motor cycle, changing the outputs as needed.<br />
//Check for motor state changes<br />
switch (motorflag) {<br />
case 0: break; //Do nothing (motor off)<br />
case 1: //Start motor cycle<br />
WriteTimer3(0); //Reset timer to 0<br />
IN1 = 0; //Set H bridge to forward drive<br />
IN2 = 1;<br />
EN = 1;<br />
motorflag = 2; //In drivedown cycle<br />
break;<br />
case 2: //Drivedown cycle<br />
if (ReadTimer3() > DRIVETIME) { //Wait for DRIVETIME to pass<br />
WriteTimer3(0); //Reset timer to 0<br />
motorflag = 3; //In driveup phase<br />
IN2 = 0; //Switch H bridge<br />
IN1 = 1;<br />
}<br />
break;<br />
case 3: //Driveup cycle<br />
if (ReadTimer3() > BACKTIME) { //Wait for BACKTIME to pass<br />
WriteTimer3(0); //Reset timer to 0<br />
motorflag = 4; //In coast/wait phase<br />
EN = 0; //Turn off motor<br />
}<br />
break;<br />
case 4: //coast / wait cycle<br />
if (ReadTimer3() > WAITTIME) {<br />
motorflag = 5; //more wait<br />
WriteTimer3(0);<br />
}<br />
break;<br />
case 5: //Additional wait cycle<br />
if (ReadTimer3() > WAITTIME) {<br />
//Done!<br />
motorflag = 0; //Motor off (can now be triggered again)<br />
}<br />
break;<br />
//Static wait cycles<br />
case 6:<br />
if (ReadTimer3() > WAITTIME) {<br />
motorflag = 7; <br />
WriteTimer3(0);<br />
}<br />
break;<br />
case 7:<br />
if (ReadTimer3() > WAITTIME) {<br />
WriteTimer3(0);<br />
motorflag = 8; <br />
}<br />
break;<br />
case 8:<br />
if (ReadTimer3() > WAITTIME) {<br />
motorflag = 0; <br />
}<br />
break;<br />
}<br />
<br />
Delayms(10);<br />
}<br />
return 0;<br />
}//end main<br />
<br />
Write chip function, which is a support function that writes data to the music chips.<br />
/* write_chip: This writes the given byte "data" to the "address" (0 or 1)<br />
on a YMZ284. It is assumed that the chip select is already low */<br />
void write_chip(int address,int data) {<br />
//Write address<br />
portout(address);<br />
A0 = 0; //select address mode<br />
Delayus(1); //setup time<br />
NWR = 0; //Write cycle begin<br />
Delayus(1); //hold time<br />
NWR = 1; //Write cycle end<br />
<br />
//Write data<br />
portout(data);<br />
A0 = 1; //select data mode<br />
Delayus(1); //setup time<br />
NWR = 0; //write cycle begin<br />
Delayus(1); //hold time<br />
NWR = 1; //write cycle end<br />
}<br />
Port out function<br />
/* port_out: This function outputs a byte to the D0-D7 port */<br />
void portout(int byte) {<br />
// Note: !! converts an integer expression to a boolean (1 or 0).<br />
D0 = !!(byte & 1);<br />
D1 = !!(byte & 2); <br />
D2 = !!(byte & 4); <br />
D3 = !!(byte & 8);<br />
D4 = !!(byte & 16);<br />
D5 = !!(byte & 32); <br />
D6 = !!(byte & 64); <br />
D7 = !!(byte & 128);<br />
}<br />
Channel switch function, which writes a new mixer value to turn a channel on or off.<br />
/* channelSw: Generic channel switch function <br />
This writes a new mixer value to chip "chip". The mixer value<br />
depends on "chn", the channel turning on or off, "state" whether<br />
the channel is turning on or off, and "*mixer", a pointer to<br />
the old mixer value. */<br />
void channelSw(int chip, int chn, int *mixer, int state) {<br />
/* Enable music chip */<br />
switch(chip) {<br />
case 1: CS1 = 0; break;<br />
case 2: CS2 = 0; break;<br />
case 3: CS3 = 0; break;<br />
case 4: CS4 = 0; break;<br />
}<br />
/* Write mixer value */<br />
/* This writes to channel 7 (mixer channel) the _new_ mixer value, which is<br />
the old mixer value with a particular bit flipped based on "chn" and "state" */<br />
write_chip(0x07, (*mixer) = state ? (*mixer) & (0xFF & ~chn) : (*mixer) | chn);<br />
/* Disable chip */<br />
switch(chip) {<br />
case 1: CS1 = 1; break;<br />
case 2: CS2 = 1; break;<br />
case 3: CS3 = 1; break;<br />
case 4: CS4 = 1; break;<br />
}<br />
<br />
}<br />
Interrupt functions<br />
//External interrupt for motor driving<br />
void __ISR(_EXTERNAL_3_VECTOR, ipl6) _SingleVectorHandler(void) {<br />
if (motorflag == 0) {<br />
motorflag = 1; //Start motor cycle<br />
}<br />
//If on already, do nothing<br />
mINT3ClearIntFlag();<br />
}<br />
<br />
//External interrupt for motor waiting<br />
void __ISR(_EXTERNAL_4_VECTOR, ipl5) _SingleVectorHandler2(void) {<br />
if (motorflag == 0) {<br />
WriteTimer3(0);<br />
motorflag = 6; //Start wait cycle<br />
}<br />
//If on already, do nothing<br />
mINT4ClearIntFlag();<br />
}<br />
<br />
=Results= <br />
<br />
The project turned out to be a decent success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. Also, for optimum functioning, the system had to be tuned for some users, adjusting the potentiometers (shown in the circuit diagram above) until a robust signal was attained.<br />
<br />
Furthermore, using the heart rate monitor was complicated by its sensitivity to movement artifact. The system worked best when one user provided the heartbeat, and another user wore the music suit. The system was decent at accounting for larger movements initiated from the shoulder or elbow, as long as the wrist and fingers stayed relatively rigid along with the arm. However, movement initiated from the wrist or finger caused a large influx of noise into the system, requiring at least 5 seconds to smooth out once the user stopped those movements.<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements, and we were also constrained by having only the notes of the pentatonic scale at our disposal. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*Tilt switches activated each of the six notes when they were supposed to<br />
**The YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*Tones were in tune and formed fun melodies and chords as the body moved<br />
*Drum actuator hit the drum reliably and with good tone<br />
*Heart rate monitor usually showed clear peaks on a scope, but was finicky in practice<br />
<br />
'''Room for improvements'''<br />
*Tilt switches sometimes bounced, so the beginning and particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' Add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*The shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' Create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*The straps could be easier to put on<br />
**''Possible Solution:'' Maybe integrate them into a single garment, like a onesie<br />
*The heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' Improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Alternatively, implement a complicated peak detection algorithm on the PIC. <br />
<br />
<br />
We believe this project, if expanded and refined, could have some really fun and useful applications. The music suit idea is not new, but it is not widespread. A device like this could be useful in music therapy, where therapists use music to help patients heal from a variety of maladies. Or, this could be an engaging interactive exhibit in a children's museum. A suit could also be used to help improve proprioception, or movement awareness, in developmentally disable patients who have difficulty coordinating certain movements and can lack feedback to help them learn. Using a suit like this would make movement fun in a whole way.<br />
<br />
Making music with this device is just plain fun, and it gives a whole new meaning to the idea of 'playing with your heart.'</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:Main_music.c&diff=17199File:Main music.c2010-03-17T03:16:58Z<p>Thomas Peterson: uploaded a new version of "Image:Main music.c": Removed unneeded code</p>
<hr />
<div>Primary C file for 333 2010 team 21</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:T21_musicckt_png.PNG&diff=17192File:T21 musicckt png.PNG2010-03-17T03:12:59Z<p>Thomas Peterson: uploaded a new version of "Image:T21 musicckt png.PNG": More label fixes</p>
<hr />
<div>Team 21 music circuit</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=17108Music from the Heart -- Music Suit2010-03-16T23:22:29Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the heart beat.<br />
<br />
==Team Members==<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
'''INSERT TEAM PICTURE HERE'''<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user would simplify the process of identifying a heart beat, but attaching the sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by lots of amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High Pass Filter]] - cutoff frequency = '''INSERT'''<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]] - gain = '''INSERT'''<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low pass filter] - cutoff frequency = '''INSERT'''<br />
# Bandpass filter - Frequency range = '''INSERT'''<br />
# [[Comparators|Comparator]] - output to the PIC<br />
<br />
The complete signal processing circuit is shown below. <br />
<br />
'''INSERT CIRCUIT DIAGRAM'''<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
The sensor itself was a '''INSERT COMPONENT NAME HERE''' that was encased in open-cell foam insulation. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure. <br />
<br />
'''INSERT PICTURE OF THE HEART SENSOR'''<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, the fastest heartbeat our drum could play was 133 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the analog signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency. Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time it was enough to make the drum beating sound bad. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak detection algorithms, but they seemed to complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L298N.pdf|L298 H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
[[Image:T21_motorckt_png.PNG|thumb|center|600px|Motor Circuit (non-motor PIC connections not shown). Click for larger image.]]<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.5"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snuggly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: A hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
'''INSERT PICTURE OF DRUMSTICK-MOTORSHAFT ATTACHMENT'''<br />
<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum. A sheet metal bracket with holes in the appropriate places attached the motor to two of these tuning screws. The bracket was easily made by cutting and bending a piece of '''GAUGE''' gauge steel sheet metal to the appropriate size. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.5"x2" (aluminum would work just as well, this is what was available) <br />
*'''GAUGE''' steel sheet metal for bracket<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
<br />
[[Image:T21_musicckt_png.PNG|thumb|center|600px|Music circuit diagram. The summing amplifier is not shown. Click for larger image.]]<br />
<br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, making current stop flowing through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (left over scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
'''INSERT PICTURE OF THE WRIST AND ANKLE STRAPS'''<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move. <br />
<br />
<br />
'''INSERT PICTURE OF THE SHOULDER STRAPS'''<br />
<br />
=Code=<br />
To download the entire C file, click [[Media:Main_music.c|here]]. Below, the file is split into sections for comments.<br />
<br />
/**********************************************************<br />
* main_music.c: Main c file for "Music Suit" project<br />
* <br />
* Thomas Peterson, James Rein, Eric West<br />
* ME333 Winter 2010<br />
**********************************************************/<br />
<br />
#include <HardwareProfile.h><br />
#include <delays.c> //Delayms and Delayus functions<br />
<br />
The first section defines all the input and output pins, used with the motor, sensor, and music circuits.<br />
<br />
////////////////////////////////////////////////////<br />
//Pin Defines<br />
<br />
//YMZ Chip selects<br />
#define CS1 LATCbits.LATC2<br />
#define CS2 LATCbits.LATC3<br />
#define CS3 LATCbits.LATC4<br />
#define CS4 LATGbits.LATG6<br />
//YMZ communication pins<br />
#define NWR LATBbits.LATB0 //"Not WRite": active low write line<br />
#define A0 LATBbits.LATB6 //Address 0<br />
#define NIC LATCbits.LATC1 //"Not Input Clear": active low reset line<br />
//YMZ data pins<br />
#define D0 LATGbits.LATG9<br />
#define D1 LATEbits.LATE8<br />
#define D2 LATEbits.LATE9<br />
#define D3 LATBbits.LATB5<br />
#define D4 LATBbits.LATB4<br />
#define D5 LATBbits.LATB3<br />
#define D6 LATBbits.LATB2<br />
#define D7 LATBbits.LATB1<br />
<br />
//Tilt inputs<br />
#define ENC PORTDbits.RD3<br />
#define END PORTDbits.RD2<br />
#define ENE PORTDbits.RD1<br />
#define ENG PORTCbits.RC14<br />
#define ENA PORTCbits.RC13<br />
#define ENC5 PORTDbits.RD0<br />
<br />
//Motor outputs<br />
#define IN1 LATBbits.LATB7<br />
#define IN2 LATAbits.LATA9<br />
#define EN LATAbits.LATA10<br />
<br />
///////////////////////////////////////<br />
//Other defines<br />
//Motor times<br />
#define MS(x) x*312.5<br />
#define DRIVETIME MS(100)<br />
#define BACKTIME MS(75)<br />
#define WAITTIME MS(150)<br />
//Channels<br />
#define CHNA 1<br />
#define CHNB 2<br />
#define CHNC 4<br />
<br />
<br />
//////////////////////////////////////////<br />
//Function definitions<br />
void write_chip(int address, int data);<br />
void portout(int byte);<br />
void channelSw(int chip, int chn, int *mixer, int state);<br />
//Global variables<br />
int mixer1, mixer2;<br />
int motorflag = 0;<br />
The initialization function sets up various parts of the system.<br />
/* init() : This function runs initializations, setting up the pic and music chips */<br />
void init() {<br />
int pbClk = SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
/////////////////////////////////////////////<br />
//Setup inputs / outputs<br />
AD1PCFG = 0xFFFF; //All digital inputs<br />
TRISG &= 0xFDBF; //sets G9,G6 to 0 (output); all else remains the same<br />
TRISC &= 0xFFE1; //sets C4,C3,C2,C1 to 0s (outputs); all else remains the same<br />
TRISB &= 0xFF80; //sets B0-B6 0 (output)<br />
TRISE &= 0xFCFF; //sets E8,9 to output<br />
TRISB |= 0xF000; //sets B12,B13,B14,B15 to 1s (inputs); all else remains the same<br />
TRISC |= 0x6000; //sets C14,13 to inputs<br />
TRISD |= 0x000F; //Sets D0,D1,D2,D3 to inputs<br />
TRISDbits.TRISD4 = 0; //Output for clock<br />
//Motor outputs<br />
TRISAbits.TRISA9 = 0;<br />
TRISAbits.TRISA10 = 0;<br />
TRISBbits.TRISB7 = 0;<br />
//Pulse inputs <br />
TRISAbits.TRISA14 = 1;<br />
TRISAbits.TRISA15 = 1;<br />
//ODC setup<br />
ODCCbits.ODCC1 = 1; //open drain control; can only float high (when set high) or pull low <br />
<br />
/* Initialize outputs */<br />
//YMZs<br />
CS1 = 1;<br />
CS2 = 1;<br />
CS3 = 1;<br />
CS4 = 1;<br />
NWR = 1;<br />
A0 = 0;<br />
NIC = 1;<br />
//Motor<br />
IN2 = 0;<br />
IN1 = 0;<br />
EN = 0;<br />
<br />
Delayms(100); //wait for power supply to ramp up<br />
NIC = 0; //resets YMZ284s<br />
Delayms(10);<br />
NIC = 1;<br />
<br />
In the init() function, the pic writes all the setup data to the music chips. This includes the frequency registers, which determine the frequency of each output, the mixer, which determines which channels are on or off (1 = off), and the volume registers, which determine the volume of each channel. After the setup, the only register that is changed in normal operation is the mixer register, which merely turns channels on and off.<br />
//////////////////////////////////////////////////<br />
//Setup music chips<br />
//Setup Chip 1//<br />
CS1 = 0;<br />
write_chip(0x00,0xC7); //a low // C4<br />
write_chip(0x01,0x01); //a high<br />
write_chip(0x02,0x98); //b low // D<br />
write_chip(0x03,0x01); //b high<br />
write_chip(0x04,0x6E); //c low<br />
write_chip(0x05,0x01); //c high // E<br />
write_chip(0x07,0xFF); //mixer (0 enables)<br />
write_chip(0x08, 0x0A); //level (volume) A<br />
write_chip(0x09, 0x0A); //level (volume) B<br />
write_chip(0x0A, 0x0A); //level (volume) C<br />
CS1=1;<br />
//Setup Chip 2//<br />
CS2 = 0;<br />
write_chip(0x00,0x2C); //a low //G 392<br />
write_chip(0x01,0x01); //a high<br />
write_chip(0x02,0x0D); //b low //A 440<br />
write_chip(0x03,0x01); //b high<br />
write_chip(0x04,0xE3); //c low //C5 523.25<br />
write_chip(0x05,0x00); //c high <br />
write_chip(0x07,0xFF); //mixer (0 enables)<br />
write_chip(0x08, 0x0A); //level (volume) A<br />
write_chip(0x09, 0x0A); //level (volume) B<br />
write_chip(0x0A, 0x0A); //level (volume) C<br />
CS2=1;<br />
<br />
mixer1=0xFF;<br />
mixer2=0xFF;<br />
OC5 is used to create a PWM signal with 50% duty at 4Mhz, in effect creating a 4Mhz square wave. This is used by the music chips as a master clock.<br />
//////////////////////////////////////////////<br />
//Setup OC to create master clock for music chips (~4 Mhz)<br />
OpenOC5( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0);<br />
OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT, 20);<br />
SetDCOC5PWM(10);<br />
The interrupts are used, as mentioned above, to control when the motor is driven. The motor should drive once every rising edge.<br />
/////////////////////////////////////////////<br />
//External interrupt setups (for motor) - INT3 (A14)<br />
mINT3ClearIntFlag();<br />
mINT3IntEnable(1);<br />
mINT3SetIntPriority(6);<br />
mINT3SetEdgeMode(1); //Rising edge<br />
/////////////////////////////////////////////<br />
//External interrupt setups (for motor) - INT4 (A15)<br />
mINT4ClearIntFlag();<br />
mINT4IntEnable(1);<br />
mINT4SetIntPriority(5);<br />
mINT4SetEdgeMode(0); //Falling edge<br />
INTEnableSystemMultiVectoredInt();<br />
<br />
/////////////////////////////////////////////<br />
//Setup motor timer (used for timing delays)<br />
OpenTimer3(T3_ON | T3_PS_1_256 | T3_SOURCE_INT, 0xFFFF);<br />
ConfigIntTimer3(T3_INT_OFF);<br />
}<br />
<br />
Main function<br />
/* Main function */<br />
int main()<br />
{<br />
init(); /* Run initialization functions */<br />
Delayms(1);<br />
<br />
/* Main while loop: loop indefinietly */<br />
while (1) {<br />
In the main while loop, the PIC checks every cycle to see if an input has changed since the last cycle. Since the "mixer" variables store the current state, if an input does not match up with its valid mixer bit then it has changed recently. In this case, the channelSw() function is called to switch on or off the music channel controlled by that bit.<br />
//Check for state changes in music notes<br />
if (ENC != (mixer1 & 0x01)) { channelSw(1,CHNA,&mixer1,!ENC); } //If input ENC (enable C) is not equal to the state of channel A1 (note c), change the state<br />
if (END != (mixer1 & 0x02)) { channelSw(1,CHNB,&mixer1,!END); }<br />
if (ENE != (mixer1 & 0x04)) { channelSw(1,CHNC,&mixer1,!ENE); }<br />
if (ENG != (mixer2 & 0x01)) { channelSw(2,CHNA,&mixer2,!ENG); }<br />
if (ENA != (mixer2 & 0x02)) { channelSw(2,CHNB,&mixer2,!ENA); }<br />
if (ENC5 != (mixer2 & 0x04)) { channelSw(2,CHNC,&mixer2,!ENC5); }<br />
During the main while loop, the motor flag variable is checked to see if the motor should be driven. If so, it enters a sequence where it uses timer3 to determine the current stage in the motor cycle, changing the outputs as needed.<br />
//Check for motor state changes<br />
switch (motorflag) {<br />
case 0: break; //Do nothing (motor off)<br />
case 1: //Start motor cycle<br />
WriteTimer3(0); //Reset timer to 0<br />
IN1 = 0; //Set H bridge to forward drive<br />
IN2 = 1;<br />
EN = 1;<br />
motorflag = 2; //In drivedown cycle<br />
break;<br />
case 2: //Drivedown cycle<br />
if (ReadTimer3() > DRIVETIME) { //Wait for DRIVETIME to pass<br />
WriteTimer3(0); //Reset timer to 0<br />
motorflag = 3; //In driveup phase<br />
IN2 = 0; //Switch H bridge<br />
IN1 = 1;<br />
}<br />
break;<br />
case 3: //Driveup cycle<br />
if (ReadTimer3() > BACKTIME) { //Wait for BACKTIME to pass<br />
WriteTimer3(0); //Reset timer to 0<br />
motorflag = 4; //In coast/wait phase<br />
EN = 0; //Turn off motor<br />
}<br />
break;<br />
case 4: //coast / wait cycle<br />
if (ReadTimer3() > WAITTIME) {<br />
motorflag = 5; //more wait<br />
WriteTimer3(0);<br />
}<br />
break;<br />
case 5: //Additional wait cycle<br />
if (ReadTimer3() > WAITTIME) {<br />
//Done!<br />
motorflag = 0; //Motor off (can now be triggered again)<br />
}<br />
break;<br />
//Static wait cycles<br />
case 6:<br />
if (ReadTimer3() > WAITTIME) {<br />
motorflag = 7; <br />
WriteTimer3(0);<br />
}<br />
break;<br />
case 7:<br />
if (ReadTimer3() > WAITTIME) {<br />
WriteTimer3(0);<br />
motorflag = 8; <br />
}<br />
break;<br />
case 8:<br />
if (ReadTimer3() > WAITTIME) {<br />
motorflag = 0; <br />
}<br />
break;<br />
}<br />
<br />
Delayms(10);<br />
}<br />
return 0;<br />
}//end main<br />
<br />
Write chip function, which is a support function that writes data to the music chips.<br />
/* write_chip: This writes the given byte "data" to the "address" (0 or 1)<br />
on a YMZ284. It is assumed that the chip select is already low */<br />
void write_chip(int address,int data) {<br />
//Write address<br />
portout(address);<br />
A0 = 0; //select address mode<br />
Delayus(1); //setup time<br />
NWR = 0; //Write cycle begin<br />
Delayus(1); //hold time<br />
NWR = 1; //Write cycle end<br />
<br />
//Write data<br />
portout(data);<br />
A0 = 1; //select data mode<br />
Delayus(1); //setup time<br />
NWR = 0; //write cycle begin<br />
Delayus(1); //hold time<br />
NWR = 1; //write cycle end<br />
}<br />
Port out function<br />
/* port_out: This function outputs a byte to the D0-D7 port */<br />
void portout(int byte) {<br />
// Note: !! converts an integer expression to a boolean (1 or 0).<br />
D0 = !!(byte & 1);<br />
D1 = !!(byte & 2); <br />
D2 = !!(byte & 4); <br />
D3 = !!(byte & 8);<br />
D4 = !!(byte & 16);<br />
D5 = !!(byte & 32); <br />
D6 = !!(byte & 64); <br />
D7 = !!(byte & 128);<br />
}<br />
Channel switch function, which writes a new mixer value to turn a channel on or off.<br />
/* channelSw: Generic channel switch function <br />
This writes a new mixer value to chip "chip". The mixer value<br />
depends on "chn", the channel turning on or off, "state" whether<br />
the channel is turning on or off, and "*mixer", a pointer to<br />
the old mixer value. */<br />
void channelSw(int chip, int chn, int *mixer, int state) {<br />
/* Enable music chip */<br />
switch(chip) {<br />
case 1: CS1 = 0; break;<br />
case 2: CS2 = 0; break;<br />
case 3: CS3 = 0; break;<br />
case 4: CS4 = 0; break;<br />
}<br />
/* Write mixer value */<br />
/* This writes to channel 7 (mixer channel) the _new_ mixer value, which is<br />
the old mixer value with a particular bit flipped based on "chn" and "state" */<br />
write_chip(0x07, (*mixer) = state ? (*mixer) & (0xFF & ~chn) : (*mixer) | chn);<br />
/* Disable chip */<br />
switch(chip) {<br />
case 1: CS1 = 1; break;<br />
case 2: CS2 = 1; break;<br />
case 3: CS3 = 1; break;<br />
case 4: CS4 = 1; break;<br />
}<br />
<br />
}<br />
Interrupt functions<br />
//External interrupt for motor driving<br />
void __ISR(_EXTERNAL_3_VECTOR, ipl6) _SingleVectorHandler(void) {<br />
if (motorflag == 0) {<br />
motorflag = 1; //Start motor cycle<br />
}<br />
//If on already, do nothing<br />
mINT3ClearIntFlag();<br />
}<br />
<br />
//External interrupt for motor waiting<br />
void __ISR(_EXTERNAL_4_VECTOR, ipl5) _SingleVectorHandler2(void) {<br />
if (motorflag == 0) {<br />
WriteTimer3(0);<br />
motorflag = 6; //Start wait cycle<br />
}<br />
//If on already, do nothing<br />
mINT4ClearIntFlag();<br />
}<br />
<br />
=Results= <br />
<br />
The project turned out to be a mild success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. <br />
<br />
'''PROBABLY NEED MORE TECHNICAL RESULTS HERE, BUT I'M NOT SURE WHAT TO SAY RIGHT NOW'''<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*tilt switches activated each of the six notes when they were supposed to<br />
**the YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*tones were in tune and formed fun melodies and chords as the body moved<br />
*drum actuator hit the drum reliably and with good tone<br />
*heart rate monitor usually showed clear peaks on a scope, but was finnicky in practice<br />
<br />
'''Room for improvements'''<br />
*tilt switches sometimes bounced, so the beginning, or particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*the shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*the straps could be easier to put on<br />
**''Possible Solution:'' maybe integrate them into a single garment, like a onesie<br />
*the heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Or implement a complicated peak detection algorithm on the PIC. <br />
<br />
'''JAMES SHOULD ADD SOME KIND OF HEARTWARMING CONCLUSION CUZ HE'S GOOD AT THAT STUFF'''</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=17091Music from the Heart -- Music Suit2010-03-16T23:11:37Z<p>Thomas Peterson: </p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the heart beat.<br />
<br />
==Team Members==<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
'''INSERT TEAM PICTURE HERE'''<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user would simplify the process of identifying a heart beat, but attaching the sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by lots of amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High Pass Filter]] - cutoff frequency = '''INSERT'''<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]] - gain = '''INSERT'''<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low pass filter] - cutoff frequency = '''INSERT'''<br />
# Bandpass filter - Frequency range = '''INSERT'''<br />
# [[Comparators|Comparator]] - output to the PIC<br />
<br />
The complete signal processing circuit is shown below. <br />
<br />
'''INSERT CIRCUIT DIAGRAM'''<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
The sensor itself was a '''INSERT COMPONENT NAME HERE''' that was encased in open-cell foam insulation. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure. <br />
<br />
'''INSERT PICTURE OF THE HEART SENSOR'''<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, the fastest heartbeat our drum could play was 133 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the analog signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency. Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time it was enough to make the drum beating sound bad. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak detection algorithms, but they seemed to complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L298N.pdf|L298 H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
[[Image:T21_motorckt_png.PNG|thumb|center|600px|Motor Circuit (non-motor PIC connections not shown). Click for larger image.]]<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.5"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snuggly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: A hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
'''INSERT PICTURE OF DRUMSTICK-MOTORSHAFT ATTACHMENT'''<br />
<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum. A sheet metal bracket with holes in the appropriate places attached the motor to two of these tuning screws. The bracket was easily made by cutting and bending a piece of '''GAUGE''' gauge steel sheet metal to the appropriate size. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.5"x2" (aluminum would work just as well, this is what was available) <br />
*'''GAUGE''' steel sheet metal for bracket<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
<br />
[[Image:T21_musicckt_png.PNG|thumb|center|600px|Music circuit diagram. The summing amplifier is not shown. Click for larger image.]]<br />
<br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, making current stop flowing through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (left over scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
'''INSERT PICTURE OF THE WRIST AND ANKLE STRAPS'''<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move. <br />
<br />
<br />
'''INSERT PICTURE OF THE SHOULDER STRAPS'''<br />
<br />
=Code=<br />
To download the entire C file, click [[Media:Main_music.c|here]].<br />
<br />
=Results= <br />
<br />
The project turned out to be a mild success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. <br />
<br />
'''PROBABLY NEED MORE TECHNICAL RESULTS HERE, BUT I'M NOT SURE WHAT TO SAY RIGHT NOW'''<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*tilt switches activated each of the six notes when they were supposed to<br />
**the YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*tones were in tune and formed fun melodies and chords as the body moved<br />
*drum actuator hit the drum reliably and with good tone<br />
*heart rate monitor usually showed clear peaks on a scope, but was finnicky in practice<br />
<br />
'''Room for improvements'''<br />
*tilt switches sometimes bounced, so the beginning, or particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*the shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*the straps could be easier to put on<br />
**''Possible Solution:'' maybe integrate them into a single garment, like a onesie<br />
*the heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Or implement a complicated peak detection algorithm on the PIC. <br />
<br />
'''JAMES SHOULD ADD SOME KIND OF HEARTWARMING CONCLUSION CUZ HE'S GOOD AT THAT STUFF'''</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:Main_music.c&diff=17088File:Main music.c2010-03-16T23:10:39Z<p>Thomas Peterson: Primary C file for 333 2010 team 21</p>
<hr />
<div>Primary C file for 333 2010 team 21</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:T21_musicckt_png.PNG&diff=17086File:T21 musicckt png.PNG2010-03-16T23:07:36Z<p>Thomas Peterson: uploaded a new version of "Image:T21 musicckt png.PNG": Added chip label</p>
<hr />
<div>Team 21 music circuit</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=17084Music from the Heart -- Music Suit2010-03-16T23:06:13Z<p>Thomas Peterson: /* Music Tones - Electrical Design */</p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the heart beat.<br />
<br />
==Team Members==<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
'''INSERT TEAM PICTURE HERE'''<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user would simplify the process of identifying a heart beat, but attaching the sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by lots of amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High Pass Filter]] - cutoff frequency = '''INSERT'''<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]] - gain = '''INSERT'''<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low pass filter] - cutoff frequency = '''INSERT'''<br />
# Bandpass filter - Frequency range = '''INSERT'''<br />
# [[Comparators|Comparator]] - output to the PIC<br />
<br />
The complete signal processing circuit is shown below. <br />
<br />
'''INSERT CIRCUIT DIAGRAM'''<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
The sensor itself was a '''INSERT COMPONENT NAME HERE''' that was encased in open-cell foam insulation. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure. <br />
<br />
'''INSERT PICTURE OF THE HEART SENSOR'''<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, the fastest heartbeat our drum could play was 133 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the analog signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency. Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time it was enough to make the drum beating sound bad. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak detection algorithms, but they seemed to complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L298N.pdf|L298 H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
[[Image:T21_motorckt_png.PNG|thumb|center|600px|Motor Circuit (non-motor PIC connections not shown). Click for larger image.]]<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.5"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snuggly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: A hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
'''INSERT PICTURE OF DRUMSTICK-MOTORSHAFT ATTACHMENT'''<br />
<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum. A sheet metal bracket with holes in the appropriate places attached the motor to two of these tuning screws. The bracket was easily made by cutting and bending a piece of '''GAUGE''' gauge steel sheet metal to the appropriate size. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.5"x2" (aluminum would work just as well, this is what was available) <br />
*'''GAUGE''' steel sheet metal for bracket<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
<br />
[[Image:T21_musicckt_png.PNG|thumb|center|600px|Music circuit diagram. The summing amplifier is not shown. Click for larger image.]]<br />
<br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, making current stop flowing through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (left over scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
'''INSERT PICTURE OF THE WRIST AND ANKLE STRAPS'''<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move. <br />
<br />
<br />
'''INSERT PICTURE OF THE SHOULDER STRAPS'''<br />
<br />
=Results= <br />
<br />
The project turned out to be a mild success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. <br />
<br />
'''PROBABLY NEED MORE TECHNICAL RESULTS HERE, BUT I'M NOT SURE WHAT TO SAY RIGHT NOW'''<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*tilt switches activated each of the six notes when they were supposed to<br />
**the YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*tones were in tune and formed fun melodies and chords as the body moved<br />
*drum actuator hit the drum reliably and with good tone<br />
*heart rate monitor usually showed clear peaks on a scope, but was finnicky in practice<br />
<br />
'''Room for improvements'''<br />
*tilt switches sometimes bounced, so the beginning, or particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*the shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*the straps could be easier to put on<br />
**''Possible Solution:'' maybe integrate them into a single garment, like a onesie<br />
*the heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Or implement a complicated peak detection algorithm on the PIC. <br />
<br />
'''JAMES SHOULD ADD SOME KIND OF HEARTWARMING CONCLUSION CUZ HE'S GOOD AT THAT STUFF'''</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:T21_musicckt_png.PNG&diff=17083File:T21 musicckt png.PNG2010-03-16T23:05:26Z<p>Thomas Peterson: Team 21 music circuit</p>
<hr />
<div>Team 21 music circuit</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=17066Music from the Heart -- Music Suit2010-03-16T22:32:08Z<p>Thomas Peterson: /* Drum actuator - Electrical design */</p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the heart beat.<br />
<br />
==Team Members==<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
'''INSERT TEAM PICTURE HERE'''<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user would simplify the process of identifying a heart beat, but attaching the sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by lots of amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High Pass Filter]] - cutoff frequency = '''INSERT'''<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]] - gain = '''INSERT'''<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low pass filter] - cutoff frequency = '''INSERT'''<br />
# Bandpass filter - Frequency range = '''INSERT'''<br />
# [[Comparators|Comparator]] - output to the PIC<br />
<br />
The complete signal processing circuit is shown below. <br />
<br />
'''INSERT CIRCUIT DIAGRAM'''<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
The sensor itself was a '''INSERT COMPONENT NAME HERE''' that was encased in open-cell foam insulation. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure. <br />
<br />
'''INSERT PICTURE OF THE HEART SENSOR'''<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, the fastest heartbeat our drum could play was 133 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the analog signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency. Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time it was enough to make the drum beating sound bad. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak detection algorithms, but they seemed to complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L298N.pdf|L298 H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
[[Image:T21_motorckt_png.PNG|thumb|center|600px|Motor Circuit (non-motor PIC connections not shown). Click for larger image.]]<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.5"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snuggly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: A hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
'''INSERT PICTURE OF DRUMSTICK-MOTORSHAFT ATTACHMENT'''<br />
<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum. A sheet metal bracket with holes in the appropriate places attached the motor to two of these tuning screws. The bracket was easily made by cutting and bending a piece of '''GAUGE''' gauge steel sheet metal to the appropriate size. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.5"x2" (aluminum would work just as well, this is what was available) <br />
*'''GAUGE''' steel sheet metal for bracket<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
<br />
'''INSERT CIRCUIT DIAGRAM OF YMZs ATTACHED TO THE PIC AND THE SUMMING CIRCUIT''', unless we feel the link in the mechatronics wiki is sufficient to describe the summing circuit. <br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, making current stop flowing through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (left over scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
'''INSERT PICTURE OF THE WRIST AND ANKLE STRAPS'''<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move. <br />
<br />
<br />
'''INSERT PICTURE OF THE SHOULDER STRAPS'''<br />
<br />
=Results= <br />
<br />
The project turned out to be a mild success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. <br />
<br />
'''PROBABLY NEED MORE TECHNICAL RESULTS HERE, BUT I'M NOT SURE WHAT TO SAY RIGHT NOW'''<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*tilt switches activated each of the six notes when they were supposed to<br />
**the YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*tones were in tune and formed fun melodies and chords as the body moved<br />
*drum actuator hit the drum reliably and with good tone<br />
*heart rate monitor usually showed clear peaks on a scope, but was finnicky in practice<br />
<br />
'''Room for improvements'''<br />
*tilt switches sometimes bounced, so the beginning, or particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*the shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*the straps could be easier to put on<br />
**''Possible Solution:'' maybe integrate them into a single garment, like a onesie<br />
*the heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Or implement a complicated peak detection algorithm on the PIC. <br />
<br />
'''JAMES SHOULD ADD SOME KIND OF HEARTWARMING CONCLUSION CUZ HE'S GOOD AT THAT STUFF'''</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=File:T21_motorckt_png.PNG&diff=17065File:T21 motorckt png.PNG2010-03-16T22:28:11Z<p>Thomas Peterson: Motor circuit for ME333 2010 Team21</p>
<hr />
<div>Motor circuit for ME333 2010 Team21</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=17064Music from the Heart -- Music Suit2010-03-16T22:27:27Z<p>Thomas Peterson: /* Drum actuator - Electrical design */</p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the heart beat.<br />
<br />
==Team Members==<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
'''INSERT TEAM PICTURE HERE'''<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user would simplify the process of identifying a heart beat, but attaching the sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by lots of amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High Pass Filter]] - cutoff frequency = '''INSERT'''<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]] - gain = '''INSERT'''<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low pass filter] - cutoff frequency = '''INSERT'''<br />
# Bandpass filter - Frequency range = '''INSERT'''<br />
# [[Comparators|Comparator]] - output to the PIC<br />
<br />
The complete signal processing circuit is shown below. <br />
<br />
'''INSERT CIRCUIT DIAGRAM'''<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
The sensor itself was a '''INSERT COMPONENT NAME HERE''' that was encased in open-cell foam insulation. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure. <br />
<br />
'''INSERT PICTURE OF THE HEART SENSOR'''<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, the fastest heartbeat our drum could play was 133 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the analog signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency. Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time it was enough to make the drum beating sound bad. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak detection algorithms, but they seemed to complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L298N.pdf|L298 H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
'''INSERT CIRCUIT DIAGRAM OF H-BRIDGE AND PIC INTERFACE'''<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.5"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snuggly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: A hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
'''INSERT PICTURE OF DRUMSTICK-MOTORSHAFT ATTACHMENT'''<br />
<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum. A sheet metal bracket with holes in the appropriate places attached the motor to two of these tuning screws. The bracket was easily made by cutting and bending a piece of '''GAUGE''' gauge steel sheet metal to the appropriate size. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.5"x2" (aluminum would work just as well, this is what was available) <br />
*'''GAUGE''' steel sheet metal for bracket<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
<br />
'''INSERT CIRCUIT DIAGRAM OF YMZs ATTACHED TO THE PIC AND THE SUMMING CIRCUIT''', unless we feel the link in the mechatronics wiki is sufficient to describe the summing circuit. <br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, making current stop flowing through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (left over scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
'''INSERT PICTURE OF THE WRIST AND ANKLE STRAPS'''<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move. <br />
<br />
<br />
'''INSERT PICTURE OF THE SHOULDER STRAPS'''<br />
<br />
=Results= <br />
<br />
The project turned out to be a mild success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. <br />
<br />
'''PROBABLY NEED MORE TECHNICAL RESULTS HERE, BUT I'M NOT SURE WHAT TO SAY RIGHT NOW'''<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*tilt switches activated each of the six notes when they were supposed to<br />
**the YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*tones were in tune and formed fun melodies and chords as the body moved<br />
*drum actuator hit the drum reliably and with good tone<br />
*heart rate monitor usually showed clear peaks on a scope, but was finnicky in practice<br />
<br />
'''Room for improvements'''<br />
*tilt switches sometimes bounced, so the beginning, or particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*the shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*the straps could be easier to put on<br />
**''Possible Solution:'' maybe integrate them into a single garment, like a onesie<br />
*the heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Or implement a complicated peak detection algorithm on the PIC. <br />
<br />
'''JAMES SHOULD ADD SOME KIND OF HEARTWARMING CONCLUSION CUZ HE'S GOOD AT THAT STUFF'''</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=17018Music from the Heart -- Music Suit2010-03-16T21:42:32Z<p>Thomas Peterson: /* Drum actuator - Electrical design */</p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the heart beat.<br />
<br />
==Team Members==<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
'''INSERT TEAM PICTURE HERE'''<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user would simplify the process of identifying a heart beat, but attaching the sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by lots of amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High Pass Filter]] - cutoff frequency = '''INSERT'''<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]] - gain = '''INSERT'''<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low pass filter] - cutoff frequency = '''INSERT'''<br />
# Bandpass filter - Frequency range = '''INSERT'''<br />
# [[Comparators|Comparator]] - output to the PIC<br />
<br />
The complete signal processing circuit is shown below. <br />
<br />
'''INSERT CIRCUIT DIAGRAM'''<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
The sensor itself was a '''INSERT COMPONENT NAME HERE''' that was encased in open-cell foam insulation. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure. <br />
<br />
'''INSERT PICTURE OF THE HEART SENSOR'''<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, the fastest heartbeat our drum could play was 133 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the analog signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency. Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time it was enough to make the drum beating sound bad. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak detection algorithms, but they seemed to complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L293D.pdf|L293D H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
'''INSERT CIRCUIT DIAGRAM OF H-BRIDGE AND PIC INTERFACE'''<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.5"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snuggly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: A hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
'''INSERT PICTURE OF DRUMSTICK-MOTORSHAFT ATTACHMENT'''<br />
<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum. A sheet metal bracket with holes in the appropriate places attached the motor to two of these tuning screws. The bracket was easily made by cutting and bending a piece of '''GAUGE''' gauge steel sheet metal to the appropriate size. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.5"x2" (aluminum would work just as well, this is what was available) <br />
*'''GAUGE''' steel sheet metal for bracket<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
<br />
'''INSERT CIRCUIT DIAGRAM OF YMZs ATTACHED TO THE PIC AND THE SUMMING CIRCUIT''', unless we feel the link in the mechatronics wiki is sufficient to describe the summing circuit. <br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, making current stop flowing through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (left over scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
'''INSERT PICTURE OF THE WRIST AND ANKLE STRAPS'''<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move. <br />
<br />
<br />
'''INSERT PICTURE OF THE SHOULDER STRAPS'''<br />
<br />
=Results= <br />
<br />
The project turned out to be a mild success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. <br />
<br />
'''PROBABLY NEED MORE TECHNICAL RESULTS HERE, BUT I'M NOT SURE WHAT TO SAY RIGHT NOW'''<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*tilt switches activated each of the six notes when they were supposed to<br />
**the YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*tones were in tune and formed fun melodies and chords as the body moved<br />
*drum actuator hit the drum reliably and with good tone<br />
*heart rate monitor usually showed clear peaks on a scope, but was finnicky in practice<br />
<br />
'''Room for improvements'''<br />
*tilt switches sometimes bounced, so the beginning, or particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*the shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*the straps could be easier to put on<br />
**''Possible Solution:'' maybe integrate them into a single garment, like a onesie<br />
*the heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Or implement a complicated peak detection algorithm on the PIC. <br />
<br />
'''JAMES SHOULD ADD SOME KIND OF HEARTWARMING CONCLUSION CUZ HE'S GOOD AT THAT STUFF'''</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=Music_from_the_Heart_--_Music_Suit&diff=17017Music from the Heart -- Music Suit2010-03-16T21:39:37Z<p>Thomas Peterson: /* Drum actuator - Electrical design */</p>
<hr />
<div>=Introduction=<br />
<br />
This project attempted to create a natural form of musical expression by connecting sensors to the body. Six tilt switches were attached to the wrist, ankles, and shoulders, each controlling a single pitch from the [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale]. The heart beat was obtained using [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography] on the user's finger, and this signal was used to strike a drum in sync with the heart beat.<br />
<br />
==Team Members==<br />
<br />
*Thomas Peterson (Computer Engineering, 2010)<br />
*James Rein (Biomedical Engineering and Music Cognition, 2010)<br />
*Eric West (Mechanical Engineering, 2011)<br />
<br />
'''INSERT TEAM PICTURE HERE'''<br />
<br />
<br />
=Subsystems=<br />
<br />
Although intended as a single, cohesive system that would allow the user to intuitively make music, the project was easily divided into three subsystems: heart rate monitor, drum actuation, and music tones. Below is more explanation about each subsystem. <br />
<br />
==Heart rate monitor - Concepts==<br />
<br />
<br />
In brainstorming how to translate a heart beat into a drum beat, we decided that the least intrusive method would be the best. Attaching electrodes to the user would simplify the process of identifying a heart beat, but attaching the sensors directly to the skin would be a time-consuming and personally-invasive process. Instead, we decided a finger tip sensor would be much more comfortable and easy to use. <br />
<br />
To make a finger tip sensor, we used the concepts of [http://en.wikipedia.org/wiki/Photoplethysmograph photoplethysmography]. Photoplethysmography is typically used in [http://en.wikipedia.org/wiki/Pulse_oximeter pulse oximeters] and finger-tip sensors in commercially-available devices. We originally tried to hack a heart rate monitor that we had purchased, but this proved difficult, so we decided to make our own. <br />
<br />
The basic concept of photoplethysmography is that blood reflects a certain amount of IR light, and the blood density in the finger changes as the heart pumps, so the IR reflectivity of the finger changes as the heart beats. Using an IR emitter-detector pair found in the mechatronics lab followed by lots of amplification and filtering, we were able to obtain a decent signal with peaks when the user's heart beat.<br />
<br />
<br />
===Heart rate monitor - Electrical Design===<br />
<br />
The signal coming directly out of the IR detector was very weak and noisy. To obtain just the information we desired, we ran it through the following filters and amplifiers: <br />
<br />
# [[Passive Filters#High-Pass Filter (HPF)|Passive High Pass Filter]] - cutoff frequency = '''INSERT'''<br />
# [[Operational Amplifiers (Op-Amps)#Inverting Amplifier|Inverting Amplifier]] - gain = '''INSERT'''<br />
# [http://en.wikipedia.org/wiki/Low-pass_filter#Active_electronic_realization Active Low pass filter] - cutoff frequency = '''INSERT'''<br />
# Bandpass filter - Frequency range = '''INSERT'''<br />
# [[Comparators|Comparator]] - output to the PIC<br />
<br />
The complete signal processing circuit is shown below. <br />
<br />
'''INSERT CIRCUIT DIAGRAM'''<br />
<br />
===Heart rate monitor - Mechanical Design===<br />
<br />
The sensor itself was a '''INSERT COMPONENT NAME HERE''' that was encased in open-cell foam insulation. We inserted it into the foam by drilling and filing a hole in the foam to the appropriate size, and gluing the sensor in place. A round, finger-shaped groove was filed into the foam and the sensor sat flush with the bottom of this groove. The user's finger was held in the correct position with relatively constant pressure using a latex band around the finger and the foam enclosure. <br />
<br />
'''INSERT PICTURE OF THE HEART SENSOR'''<br />
<br />
<br />
==Drum actuator - Electrical design==<br />
<br />
Once the signal from the heart rate sensor is sent to the PIC, the rising edge triggers an interrupt which starts the driving sequence for the motor that strikes the drum. The drive sequence was: drive down (into drum) 100ms, drive up 75ms, wait 150ms. The interrupt does not do the entire driving sequence, it merely flags a variable to start the sequence, which is done in the main loop, as seen in the code below.<br />
<br />
An additional interrupt was triggered by the falling edge of the signal; this falling edge interrupt added additional wait time that prevented the PIC from being "fooled" by a noisy falling edge. For instance, if the falling signal had some noise in it, the rising edge interrupt would be triggered again, thus causing the drum to strike on both the rising and falling edges of the heartbeat. We only wanted one strike per heartbeat, so the falling edge interrupt was triggered by the ''first'' falling edge, and any noise in the falling edge was ignored because the PIC was told to wait for 300ms. With a maximum delay of 150 + 300 = 450ms from the two interrupts, the fastest heartbeat our drum could play was 133 beats per minute, but we did not expect anyone to come play the instrument after running, so this was plenty high for our purposes. <br />
<br />
Initially, we tried skipping the comparator and sending the signal directly into one of the PICs analog inputs. Our intention was to use the fast Fourier transform (FFT) library to try to extract the dominant frequency. Unfortunately, we ran into various problems. First, to get the accuracy we wanted for small frequencies, the sample window had to be very long (10 seconds or more), which made the output lag changes in heart rate. Additionally, the FFT was often "messy", which messed up our original algorithm of detecting the peak frequency between .5 and 2.5 hz. The system would sometimes randomly switch between very different frequencies, and although it was right most of the time it was enough to make the drum beating sound bad. We also tried algorithms to find the [http://en.wikipedia.org/wiki/Fundamental_frequency fundamental frequency] using the entire FFT (instead of just the .5-2.5 hz range), but this was still unreliable. We considered FFT peak detection algorithms, but they seemed to complex for the time and computing power we had available. Because of all this, we went with the simple comparator in the end.<br />
<br />
The motor for driving the drumstick was driven by an [[media:L293D.pdf|L293D H-bridge]]. See [[Driving a high current DC Motor using an H-bridge]] for more information on how this works. Our particular circuit diagram is shown below: <br />
<br />
'''INSERT CIRCUIT DIAGRAM OF H-BRIDGE AND PIC INTERFACE'''<br />
<br />
====Drum actuator - Mechanical Design====<br />
<br />
We wanted to use a real drum for our drum beat, so our challenge was to attach a motor and drumstick to a drum. We chose a small rack tom for its resonant tone that is reminiscent of a heart beat. <br />
<br />
To attach the drumstick to the motor shaft, we used a small block of acrylic (.5"x.5"x2"). The drumstick was cut down to 8" in length so that it would require less torque from the motor. In one end of the block, a hole was drilled and the drumstick was sanded to the correct diameter in order to snuggly fit into the hole. To ensure that the drumstick could not slide out of the block, a screw was placed in a hole drilled through both the stick and the block. The other end of the block was attached to the motor shaft using a split-clamp method: A hole the size of the motor shaft was drilled through the block, and then a slit was cut from the end of the block to the hole, allowing a screw to tightly clamp the block around the motor. <br />
<br />
'''INSERT PICTURE OF DRUMSTICK-MOTORSHAFT ATTACHMENT'''<br />
<br />
To attach the motor-stick assembly to the drum, we utilized the tuning screws on the drum. A sheet metal bracket with holes in the appropriate places attached the motor to two of these tuning screws. The bracket was easily made by cutting and bending a piece of '''GAUGE''' gauge steel sheet metal to the appropriate size. Ideally, this part would have been made out of one continuous piece of sheet metal, but we could not find a large enough sheet available in the shop, so we used rivets to connect two smaller pieces together. <br />
<br />
After testing the drum striking, we decided it was too loud. To dampen the sound, we taped a piece of foam core and a shop rag to the head of the drum where the stick strikes it.<br />
<br />
'''Parts list for drum actuator'''<br />
*Bargain drumstick, at least .5" in diameter<br />
*Small block of acrylic, .5"x.5"x2" (aluminum would work just as well, this is what was available) <br />
*'''GAUGE''' steel sheet metal for bracket<br />
*Screws and nuts<br />
*Foam core, shop rag, tape for dampening<br />
<br />
==Music Tones - Electrical Design==<br />
The music tones were activated by tilt switches and generated using a [http://pdf1.alldatasheet.com/datasheet-pdf/view/82065/YAMAHA/YMZ284.html YMZ284] chip and were output through a 1/8" jack to a standard set of computer speakers.<br />
<br />
Each YMZ284 is capable of producing and mixing any combination of three tones. Since we wanted to have six notes from a [http://en.wikipedia.org/wiki/Pentatonic_scale pentatonic scale], we needed two of these chips and used an [[Operational Amplifiers (Op-Amps)#Summer|summing opamp circuit]] to combine outputs from each chip. We envisioned a possibility of adding [http://en.wikipedia.org/wiki/Overtone overtones] to each note, to make them sound better and more like a real instrument, so we ended up having four YMZ284s attached to our board. When we tried adding an overtone an octave above the fundamental frequency, it ended up sounding harsh and whiny. We decided the tones sounded better with no overtones, so we are only using 2 of the 4 chips on our board. <br />
<br />
The YMZ284 communicates with the PIC over an 8-bit databus, plus additional pins for chip select, address, write enable, and reset. All four of our YMZ284s shared all of their pins ''except'' the chip select line; each required their own individual chip select line so that the PIC could specify with which chips it wanted to talk. The circuit diagram below shows how the chips were connected to the PIC and the summing circuit that followed. <br />
<br />
<br />
<br />
'''INSERT CIRCUIT DIAGRAM OF YMZs ATTACHED TO THE PIC AND THE SUMMING CIRCUIT''', unless we feel the link in the mechatronics wiki is sufficient to describe the summing circuit. <br />
<br />
<br />
<br />
===Tilt switches - Electrical Design===<br />
In order to detect when the limbs of the user had moved past a certain critical angle, we chose to use tilt switches. We purchased our tilt switches for $2 each from [http://www.goldmine-elec-products.com/prodinfo.asp?number=G16881 Electronics Goldmine]. We chose to use tilt switches instead of an accelerometer because the switches were much cheaper and we figured they would be easier to work with. <br />
<br />
The tilt switches we chose are optically-based with an emitter and detector that are selectively blocked by a small ball. When the critical angle is achieved, the tiny ball rolls up a ramp, allowing the emitter to shine directly on the detector, and current flows. When the angle is neutral, the ramp forces the ball to block the light, making current stop flowing through the phototransistor. The "ramp" is really an inverted cone, so the switch can be activated by a change in angle in any direction away from the neutral position. We purchased three different tilt switches to try, each with a different critical angle - 15, 30, and 45 degrees. The configuration we ended up using was: <br />
<br />
*30 degrees for shoulders and ankles<br />
*45 degrees for elbows<br />
<br />
In hindsight, we would have used all 45-degree switches because they produced the sharpest jump from off to on. The 15-degree switches were practically unusable because they created a large amount of noise as they switched on and off. <br />
<br />
We used the tilt switches to provide high-low inputs to the PIC to tell it when to turn on any of the six tones. By attaching these switches to the body such that they are in the neutral position when the body is relaxed, we were able to control the six tones by moving the body in relatively natural ways. <br />
<br />
===Tilt switches - Mechanical Design===<br />
The switches were attached to the user's body using adjustable straps and buckles. The wrist and ankle sensors were simply a loop of webbing with an adjustable buckle attached. The straps could be tightened or loosened to fit any size user. To attach the switch to the straps, we created an enclosure for each switch out of high-density foam (left over scraps from DSGN 307). The switch was jammed into a hole in the foam so that it could not easily be moved. The foam was then taped to the straps using electrical tape.<br />
<br />
<br />
'''INSERT PICTURE OF THE WRIST AND ANKLE STRAPS'''<br />
<br />
<br />
The shoulder straps were a bit more involved. Two loops with buckles, similar to the wrist and ankle straps, went over the user's shoulder and underneath their armpit. To keep these straps from sliding off the shoulders, a large strap across the user's back connected the two smaller straps together. This large strap was also used for mounting the solder board which gathered the wires from all six tilt switches and sent them to the PIC over one ribbon cable. Using this strap configuration, we found that the user could comfortably and securely wear the sensors. <br />
<br />
To attach the switches to the shoulders, we again created a foam encasement. However, electrical tape was not secure enough to hold the foam pieces to the irregularly-shaped shoulder straps. Instead, we riveted a piece of canvas to the webbing straps. We chose to use rivets instead of sewing because the sewing machine could not tightly sew the foam encasement in place. With rivets, we were able to pull the canvas tightly around the foam so that it couldn't move. <br />
<br />
<br />
'''INSERT PICTURE OF THE SHOULDER STRAPS'''<br />
<br />
=Results= <br />
<br />
The project turned out to be a mild success overall. The tilt switches worked very well for activating the music tones, but the heart rate monitor was less successful. One of the biggest issues was getting rid of very low frequency drift in the signal that made the peaks sometimes as high as 10V, but other times less 1V. Dealing with this fluctuation in peak size was the most difficult part of signal processing. As mentioned above, we tried FFT in order to establish a steady heart rate, but could not get the resolution we desired, thus we abandoned the idea. <br />
<br />
'''PROBABLY NEED MORE TECHNICAL RESULTS HERE, BUT I'M NOT SURE WHAT TO SAY RIGHT NOW'''<br />
<br />
The best result was that using the device was just plain fun. We each had a great time moving around and hearing the fun sounds that resulted, or if we were feeling ambitious, we would try to play a simple melody like "Mary Had a Little Lamb." It was rather difficult to play anything more complicated than that because it required lots of body control and precise movements. <br />
<br />
<br />
=Reflections=<br />
<br />
'''Successes'''<br />
*tilt switches activated each of the six notes when they were supposed to<br />
**the YMZ284 chips were able to produce all six notes at the same time, allowing cool chords<br />
*tones were in tune and formed fun melodies and chords as the body moved<br />
*drum actuator hit the drum reliably and with good tone<br />
*heart rate monitor usually showed clear peaks on a scope, but was finnicky in practice<br />
<br />
'''Room for improvements'''<br />
*tilt switches sometimes bounced, so the beginning, or particularly the ends of the notes sounded jagged<br />
**''Possible Solution:'' add a low pass filter to the switch signal so that the signal ramps up and down. <br />
*the shoulder straps sometimes needed adjustments for each individual in order for the sensors to be in the correct position<br />
**''Possible Solution:'' create straps with more constraints, such as a strap across the chest as well as the back, to ensure that the sensors are in the correct positions and orientations. <br />
*the straps could be easier to put on<br />
**''Possible Solution:'' maybe integrate them into a single garment, like a onesie<br />
*the heart rate monitor was not reliable. Low frequency drift made setting a comparison level nearly impossible, especially for more than one specific user.<br />
**''Possible Solution:'' improve FFT method so that desired resolution could be achieved in a short sample period (around 10 seconds). Or implement a complicated peak detection algorithm on the PIC. <br />
<br />
'''JAMES SHOULD ADD SOME KIND OF HEARTWARMING CONCLUSION CUZ HE'S GOOD AT THAT STUFF'''</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=PIC32MX:_I2C_Communication_between_PIC32s&diff=16565PIC32MX: I2C Communication between PIC32s2010-02-24T16:10:34Z<p>Thomas Peterson: /* Master Code */</p>
<hr />
<div>== Original Assignment ==<br />
<br />
'''Do not erase this section!'''<br />
<br />
Your assignment is to create code that allows two PIC32s to communicate via I2C.<br />
<br />
== Overview ==<br />
<br />
[http://www.nxp.com/acrobat_download/literature/9398/39340011.pdf I2C] is pronounced "I squared C" and stands for Inter-Integrated Circuit. This protocol was designed by Phillips Semiconductors around 1992 to allow easy communication between components on the same circuit board and can achieve transfer rates of up to 400 kbit/sec. I2C is a 2 line (plus common ground) communication method for one master device to control up to 112 slave devices. While it is possible to have multiple masters on the same I2C bus, this page will only deal with a one master configuration.<br />
<br />
<b>Basic Operation</b><br />
<br />
The two lines are named SCL1 and SDA1 where SCL1 is the CLock line and SDA1 is the DAta line. I2C requires the lines to be high unless the master or the slave is pulling the line down, so you will need to use pull up resistors on both the clock and data lines.<br />
<br />
{| border="1" cellspacing="2" cellpadding="3" align="center"<br />
|-<br />
!Line!! Name!! Description<br />
|-<br />
|SCL1||Serial Clock Line||Output from master<br />
|-<br />
|SDA1||Data Line||The single data line for sending/receiving<br />
|-<br />
|}<br />
<br />
<br />
The data line is read each time the clock line goes high, as shown in the diagram below:<br />
<br />
[[image:I2C_Data_Transfer.jpg|center]]<br />
<br />
* S - As the master pulls down the data line, the falling edge of the data line signifies the start condition.<br />
* P - As the master allows the data line to rise, the rising edge of the data line signifies the stop condition.<br />
* B - Individual bits being transferred.<br />
<br />
The data line can only change while the clock line is low. The data line is only read when the clock line is high and therefore 1 bit can be transferred per cycle of the clock line.<br />
<br />
The master initially sends a start bit followed by the 7-bit address of the slave it wishes to communicate with. The next bit represents whether it wishes to write(0) to or read(1) from the slave.<br />
<br />
The slave then responds with an acknowledge bit. The transmission continues according to whether the master is attempting to read to or write from the device.<br />
<br />
The master then ends the transmission with a stop bit. Another option is to send another start bit to continue the transfer in a combined message.<br />
<br />
== Circuit ==<br />
<br />
[[Image:I2C_circuit.PNG]]<br />
<br />
== Code ==<br />
In the code below, the master sends one of two bytes (0xAA or 0x23) to the slave at address 0x40. The master then receives a byte back from the slave. If the slave receives a 0xAA, it lights its LEDs.<br />
When the master begins sending, the slave triggers an interrupt (if the slave address is a match), which does one of four actions based on whether the master is reading or writing and whether the last byte was a data or address byte. As mentioned, when the master is writing the slave stores the byte, and when the master is reading the slave sends back the last stored byte.<br />
===Master Code===<br />
/********************************************************<br />
* I2C_Master.c: Master code for I2C communication. *<br />
* Both PICS use I2C1 module to send/ receive data.*<br />
* The master sends different values to a slave, *<br />
* which uses an interrupt to respond accordingly. *<br />
* *<br />
* Hardware: 2 PIC32MX460F512L PICs on NU32 boards *<br />
********************************************************<br />
* Thomas Peterson, James Rein, Eric West *<br />
* ME333 Winter 2010 *<br />
* File Created: 05-FEB-2010 *<br />
* Last Modified: 14-FEB-2010 *<br />
*******************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
<br />
#define Fsck 50000<br />
#define BRG_VAL ((PBCLK/2/Fsck)-2)<br />
<br />
#define Nop() asm( "nop" ) //No-operation; asm stands for assembly, using an assembly command in C. Cool!<br />
<br />
#define INPUT_A9 PORTAbits.RA9<br />
#define INPUT_A10 PORTAbits.RA10<br />
<br />
<br />
//function declaration for sending data and selecting slave address<br />
void SendData(int,unsigned int);<br />
void Delayms( unsigned t);<br />
<br />
/*<br />
This function is a delay function, causing the program to wait for approximately 4 * cnt cycles<br />
1 cycle is 1/SYSCLK seconds.<br />
*/<br />
void i2c_wait(unsigned int cnt)<br />
{<br />
while(--cnt)<br />
{<br />
Nop();<br />
Nop();<br />
}<br />
}<br />
<br />
<br />
/* Main function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states.<br />
SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Setup TRIS bits for switches and I2C pins<br />
TRISAbits.TRISA14=0;<br />
TRISAbits.TRISA15=0;<br />
<br />
//Initialize all of the LED pins<br />
mInitAllLEDs();<br />
mInitAllSwitches()<br />
<br />
unsigned char SlaveAddress; //Slave address variable to tell the master where to send the data. <br />
//Will be re-assigned for multiple slaves.<br />
<br />
//Enable I2C channel and set the baud rate to BRG_VAL)<br />
OpenI2C1( I2C_EN, BRG_VAL );<br />
<br />
int rcv; //For received data<br />
<br />
//While loop to test LED functionality <br />
while(1) {<br />
if (swProgram) { //First button pressed<br />
while(swProgram) { Nop(); } //Wait for release<br />
mLED_2_Toggle(); //Toggle LED2<br />
SendData(0xAA,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100);<br />
}<br />
if (swUser) { //Second button pressed<br />
while(swUser) { Nop(); }<br />
mLED_1_Toggle(); //Toggle LED1<br />
SendData(0x23,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100); <br />
}<br />
}//while loop ending<br />
<br />
return 0;<br />
} //ending main <br />
<br />
<br />
/*****************************************************<br />
* RcvData(unsigned int address) *<br />
* *<br />
* Gets a byte of data from I2C slave device at *<br />
* ADDRESS. *<br />
* *<br />
* Returns: Received data *<br />
****************************************************/<br />
int RcvData(unsigned int address) {<br />
StartI2C1(); //Send line start condition<br />
IdleI2C1(); //Wait to complete<br />
MasterWriteI2C1((address << 1) | 1); //Write out slave address OR 1 (read command)<br />
IdleI2C1(); //Wait to complete<br />
int rcv = MasterReadI2C1(); //Read in a value<br />
StopI2C1(); //Send line stop condition<br />
IdleI2C1(); //Wait co complete<br />
return rcv; //Return read value<br />
}<br />
<br />
<br />
<br />
/***************************************************<br />
* SendData(int data, unsigned int address) *<br />
* *<br />
* Sends a byte of data (DATA) over the I2C line *<br />
* to I2C address ADDRESS *<br />
* *<br />
* Returns: nothing *<br />
***************************************************/<br />
void SendData (int data, unsigned int address){<br />
StartI2C1(); //Send the Start Bit<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1((address << 1) | 0); //Sends the slave address over the I2C line. This must happen first so the <br />
//proper slave is selected to receive data.<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1(data); //Sends data byte over I2C line<br />
IdleI2C1(); //Wait to complete<br />
<br />
StopI2C1(); //Send the Stop condition<br />
IdleI2C1(); //Wait to complete<br />
<br />
} //end function<br />
<br />
<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < SYS_FREQ/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
===Slave Code===<br />
/***********************************************************************<br />
* PIC32 I2C Slave Code <br />
***********************************************************************/<br />
<br />
#include "GenericTypeDefs.h"<br />
#include "Compiler.h"<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
<br />
#define Fsck 50000<br />
#define BRG_VAL ((PBCLK/2/Fsck)-2)<br />
<br />
// this is the modules Slave Address<br />
#define SLAVE_ADDRESS 0x40<br />
<br />
// volatile variables to hold the switch and led states<br />
volatile unsigned char dataRead = 0;<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// InitI2C<br />
//<br />
// Perform initialisation of the I2C module to operate as a slave<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void InitI2C(void)<br />
{<br />
unsigned char temp;<br />
<br />
// Enable the I2C module with clock stretching enabled<br />
OpenI2C1(I2C_ON | I2C_7BIT_ADD | I2C_STR_EN, BRG_VAL);<br />
<br />
// set the address of the slave module, address matching is with bits<br />
// 7:1 of the message compared with bits 6:0 of the ADD SFR so we<br />
// need to shift the desired address 1 bit. <br />
I2C1ADD = SLAVE_ADDRESS; // >> 1;<br />
I2C1MSK = 0;<br />
<br />
// configure the interrupt priority for the I2C peripheral<br />
mI2C1SetIntPriority(I2C_INT_PRI_3 | I2C_INT_SLAVE);<br />
<br />
// clear pending interrupts and enable I2C interrupts<br />
mI2C1SClearIntFlag();<br />
EnableIntSI2C1;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// main routine<br />
// <br />
// This code example demonstrates using the PIC32 as an I2C slave<br />
// <br />
//<br />
///////////////////////////////////////////////////////////////////<br />
int main (void)<br />
{<br />
// set for 80MHz operation<br />
SYSTEMConfigPerformance(SYSCLK);<br />
// set the Pbus to be 40000000<br />
mOSCSetPBDIV(OSC_PB_DIV_2);<br />
// disable the JTAG port<br />
mJTAGPortEnable(0);<br />
// enable interrupts<br />
INTEnableSystemMultiVectoredInt();<br />
<br />
InitI2C();<br />
mInitAllLEDs();<br />
<br />
// main loop<br />
while (1) {<br />
/* If global variable "dataRead" is set high during interrupt, turn on all LEDs */<br />
if (dataRead == 0xAA)<br />
{<br />
mLED_0_On();<br />
mLED_1_On();<br />
mLED_2_On(); <br />
mLED_3_On(); <br />
<br />
}<br />
}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// Slave I2C interrupt handler<br />
// This handler is called when a qualifying I2C events occurs<br />
// this means that as well as Slave events <br />
// Master and Bus Collision events will also trigger this handler.<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void __ISR(_I2C_1_VECTOR, ipl3) _SlaveI2CHandler(void)<br />
{<br />
mLED_1_On();<br />
unsigned char temp;<br />
static unsigned int dIndex;<br />
<br />
// check for MASTER and Bus events and respond accordingly<br />
if (IFS0bits.I2C1MIF == 1) {<br />
mI2C1MClearIntFlag();<br />
return; <br />
}<br />
if (IFS0bits.I2C1BIF == 1) {<br />
mI2C1BClearIntFlag();<br />
return;<br />
}<br />
mLED_1_Off();<br />
mLED_2_On();<br />
<br />
// handle the incoming message<br />
if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 0 --> indicates last byte was address <br />
<br />
// reset any state variables needed by a message sequence <br />
// perform a dummy read of the address<br />
temp = SlaveReadI2C1();<br />
<br />
mLED_3_On();<br />
mLED_2_Off();<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release the clock<br />
<br />
} else if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
<br />
mLED_3_On();<br />
mLED_2_On();<br />
// writing data to our module, just store it in adcSample<br />
dataRead = SlaveReadI2C1();<br />
<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release clock stretch bit<br />
<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 1 --> indicates data transfer is output from slave<br />
// D/A bit = 0 --> indicates last byte was address<br />
mLED_0_On();<br />
mLED_2_Off();<br />
// read of the slave device, read the address <br />
temp = SlaveReadI2C1();<br />
dIndex = 0;<br />
SlaveWriteI2C1(dataRead);<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 1 --> indicates data transfer is output from slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
mLED_0_On();<br />
mLED_2_On();<br />
<br />
// output the data until the MASTER terminates the<br />
// transfer with a NACK, continuing reads return 0<br />
if (dIndex == 0) {<br />
SlaveWriteI2C1(dataRead);<br />
dIndex++;<br />
} else<br />
SlaveWriteI2C1(0);<br />
}<br />
<br />
// finally clear the slave interrupt flag<br />
mI2C1SClearIntFlag(); <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=PIC32MX:_SPI_Communication_between_PIC32s&diff=15904PIC32MX: SPI Communication between PIC32s2010-02-15T05:03:27Z<p>Thomas Peterson: /* Code */</p>
<hr />
<div>== Original Assignment ==<br />
<br />
'''Do not erase this section!'''<br />
<br />
Your assignment is to create code that allows two PIC32 boards to communicate with each other via SPI.<br />
<br />
== Overview ==<br />
<br />
SPI or [http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus Serial Peripheral Interface] is a communication method that was once used to connect devices such as printers, cameras, scanners, etc. to a desktop computer. This function has largely been taken over by USB, but SPI can still be a useful communication tool for some applications. SPI runs using a master/slave set-up and can run in full duplex mode, meaning that signals can be transmitted between the master and the slave simultaneously. There is no standard communication protocol for SPI.<br />
<br />
SPI is still used to control some peripheral devices and has some advantages over [[I2C communication between PICs|I2C]]'''CHANGE THIS TO OUR I2C PAGE!!''' (another type of serial data communication). SPI can communicate at much higher data rates than I2C. Furthermore, when multiple slaves are present, SPI requires no addressing to differentiate between these slaves. Compared to parallel buses, SPI has the additional benefit of requiring only simple wiring. However, one disadvantage when compared to I2C is that each slave must have a dedicated slave line. A slave line is not necessary when only one slave is present.<br />
<br />
<br />
Peripheral devices that still use SPI:<br />
<br />
• Converters (ADC and DAC)<br />
<br />
• Memories (EEPROM and FLASH)<br />
<br />
• Real Time Clocks (RTC)<br />
<br />
• Sensors (temperature, pressure, etc.)<br />
<br />
• Others (signal mixer, potentiometer, LCD controller, UART, CAN controller, USB controller, amplifier)<br />
<br />
<br />
<br />
<b>Basic Operation</b><br />
<br />
SPI requires three lines, and the optional slave select, and is therefore often termed the “four wire” serial bus. These four lines are described in the table below.<br />
<br />
{| border="1" cellspacing="2" cellpadding="3" align="center"<br />
|-<br />
!Line!! Name!! Description<br />
|-<br />
|SCK1||Serial Clock||Output from master<br />
|-<br />
|SDO1||Master Output, Slave Input||Output from master<br />
|-<br />
|SDI1||Master Input, Slave Output||Output from slave<br />
|-<br />
|SS1||Slave Select||Output from master (active low)<br />
|-<br />
|}<br />
<br />
<br />
The master, as its name suggests, controls all communication. By controlling the clock, the master decides when data is sent and received. Within each clock cycle a full duplex communication is carried out; each side sends and receives one bit of information. Because there is no standard communication protocol, the master can either send data or both send and receive data, depending on the needs of the application. Likewise, the slave can either receive data or both receive and send data back to the master.<br />
<br />
Using the “Slave Select” line, the master chooses which slave with which to communicate. Note that more than one slave may be selected, simply by applying a logic low to the desired SS lines, as illustrated in the schematic diagram shown above. If a given slave is not selected (its SS is high) it disregards signals sent by the master. <br />
<br />
<br />
<br />
<b>References</b><br />
<br />
[http://www.totalphase.com/support/articles/article03/ SPI Background](www.totalphase.com)<br />
<br />
[http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus SPI Wikipedia Article] (www.wikipedia.org)<br />
<br />
[http://www.mct.net/faq/spi.html More Information] (www.mct.net)<br />
<br />
== Circuit ==<br />
[[Image:SPI_circuit.PNG]]<br />
<br />
== Code ==<br />
[[Image:Pic32_spi.png||thumb|right|x500px| PIC32MX SPI Module Block Diagram (click to expand) ]]<br />
<br />
This code, coupled with the circuit diagram above, sends one of two bytes of data from the master to the slave, and back. The data bytes are 0xAA and 0x23 which are sent on different button presses (PRG and USER). Slave-LED0 toggles when 0xAA is received from the master, and slave-LED1 toggles when anything else (for example, 0x23) is received from the master.<br />
No slave select line is used since there is only one slave, but if there were multiple slaves it would have to be used.<br />
The SPI module sends and receives at the same time, so whenever a byte is written to SPI1BUF the module immediately sends out that byte on SDO which clocking with SCK and a byte is clocked in at the same time and written back to SPI1BUF. The send and receive buffers, SPI1TXB and SPI1RXB, cannot be written to directly, but instead are memory mapped to SPI1BUF. If the slave is not sending a value back when the master sends a byte, a 0x00 will be written into SPI1BUF. Because of this, in the code below the master reads from SPI1BUF after sending to clear the buffer and prevent overflow. To actually receive a byte, the master ''must'' send a byte as well. In the code below, by writing a zero to SPI1BUF, the master clocks out zero and in the slave message at the same time, at which point the slave message can be used. Since SPI is slower than the system clock, reading from SPI1BUF immediately would not result in the correct byte, instead the pic must wait for the module to finish sending, which the function getcSPI1() does automatically. Similarly, the slave sends a byte back by writing a value into SPI1BUF, but since the module is in slave mode the clock is not generated immediately. Instead the value waits in SPI1TXB until the clock is generated by the master. At this point the slave clocks out that value and in another value.<br />
<br />
<br />
<br />
===Master Code===<br />
/****************************************************<br />
* SPI_master_btwnPIC32s.c: Master code for Master- *<br />
* Slave SPI communication. Both PICs are set to *<br />
* use the SPI1 module and no SS line. A SS line *<br />
* would be used if there were more than two PICs.*<br />
* *<br />
* Hardware: 2 PIC32MX460F512L PICs on NU32 boards *<br />
****************************************************<br />
* Thomas Peterson, James Rein, Eric West *<br />
* ME333 Winter 2010 *<br />
* File Created: 05-FEB-2010 *<br />
* Last Modified: 10-FEB-2010 *<br />
****************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
//No-operation; asm stands for assembly<br />
#define Nop() asm( "nop" )<br />
<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
<br />
//function definitions<br />
void SendData(int); //Sends data<br />
void Delayms( unsigned t); //Delay fcn<br />
<br />
<br />
/* Main Function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states<br />
SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
//Initialize all of the LED pins<br />
mInitAllLEDs();<br />
mInitAllSwitches()<br />
<br />
//SPI setup<br />
int rData = SPI1BUF; //Clears receive buffer<br />
IFS0CLR = 0x03800000; //Clears any existing event (rx / tx/ fault interrupt)<br />
SPI1STATCLR = 0x40; //Clears overflow<br />
//Enables the SPI channel (channel, master mode enable | use 8 bit mode | turn on, clock divider)<br />
SpiChnOpen(1, SPI_CON_MSTEN | SPI_CON_MODE8 | SPI_CON_ON, 1024); // divide fpb by 1024, configure the I/O ports.<br />
<br />
/* Main while loop: Waits for button press to send/ receive data */<br />
while(1)<br />
{<br />
if (swProgram) //If button 1 depressed<br />
{<br />
while(swProgram) { Nop(); } //Wait for release<br />
mLED_2_Toggle(); //Toggle LED<br />
putcSPI1(0xAA); //Sends hex data 0xAA to slave<br />
Delayms(50); //delay<br />
SpiChnClrIntFlags(1); //Clear interrupt flags (Tx / Rx buffers empty)<br />
int receive = SPI1BUF; //Read SP1BUF (dummy read)<br />
SPI1BUF = 0x0; //Write SP1BUF- sets Tx flag, if not done read will not clock<br />
receive = getcSPI1(); //Generates clock and reads SDO<br />
Delayms(100);<br />
}//if loop ending<br />
<br />
<br />
if (swUser)<br />
{<br />
while(swUser) { Nop(); }<br />
mLED_1_Toggle();//Toggle LED<br />
putcSPI1(0x23); //Sends hex data 0x23 to slave<br />
Delayms(50); //delay<br />
SpiChnClrIntFlags(1); //Clear interrupt flags (Tx / Rx buffers empty)<br />
int receive = SPI1BUF; //Read SP1BUF (dummy read)<br />
SPI1BUF = 0x0; //Write SP1BUF- sets Tx flag, if not done read will not clock<br />
receive = getcSPI1(); //Generates clock and reads SDO<br />
Delayms(100);<br />
}//if loop ending<br />
}//while loop ending<br />
<br />
return 0;<br />
} //ending main <br />
<br />
<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < SYS_FREQ/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
===Slave Code===<br />
<br />
/****************************************************<br />
* SPI_slave_btwnPIC32s.c: Slave code for Master- *<br />
* Slave SPI communication. Both PICs are set to *<br />
* use the SPI1 module and no SS line. A SS line *<br />
* would be used if there were more than two PICs.*<br />
* *<br />
* Hardware: 2 PIC32MX460F512L PICs on NU32 boards *<br />
****************************************************<br />
* Thomas Peterson, James Rein, Eric West *<br />
* ME333 Winter 2010 *<br />
* File Created: 05-FEB-2010 *<br />
* Last Modified: 10-FEB-2010 *<br />
****************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
<br />
#define Nop() asm( "nop" ) //No-operation; asm stands for assembly, using an assembly command in C. Cool!<br />
<br />
<br />
//Function definition for Delay function<br />
void Delayms( unsigned t);<br />
<br />
<br />
/* Main function */<br />
int main(void)<br />
// Configure the proper PB frequency and the number of wait states. <br />
SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
/* Set TRIS bits for SPI lines (this may not be necessary */<br />
TRISDbits.TRISD10=1;<br />
TRISDbits.TRISD0=0;<br />
TRISCbits.TRISC4=1;<br />
<br />
//Initialize all of the LED pins<br />
mInitAllLEDs();<br />
mLED_3_Off();<br />
<br />
//SPI setup<br />
SPI1CON = 0; //Clears config register<br />
int rData = SPI1BUF; //Clears receive buffer<br />
IFS0CLR = 0x03800000; //Clears any existing event (rx / tx/ fault interrupt)<br />
SPI1STATCLR = 0x40; //Clears overflow<br />
//Enables the SPI channel (channel, master mode enable | use 8 bit mode | turn on, clock divider)<br />
SpiChnOpen(1, SPI_CON_SLVEN | SPI_CON_MODE8 | SPI_CON_ON, 1024); // divide fpb by 1024, configure the I/O ports.<br />
<br />
//While loop to test LED functionality <br />
while(1)<br />
{<br />
rData = SpiChnGetC(1); //Wait and read character when aviliable<br />
if (rData == 0xAA){mLED_0_Toggle();} //Toggle LED0 if 0xAA<br />
else {mLED_1_Toggle();} //Toggle LED1 if not<br />
putcSPI1(rData); //Send character back (when clock arrives)<br />
rData = SPI1BUF; //Clear recieve buffer (brevents overflow)<br />
Delayms(5);<br />
}//while loop ending<br />
<br />
return 0;<br />
} //ending main <br />
<br />
<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < SYS_FREQ/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=PIC32MX:_I2C_Communication_between_PIC32s&diff=15902PIC32MX: I2C Communication between PIC32s2010-02-15T04:49:19Z<p>Thomas Peterson: /* Master Code */</p>
<hr />
<div>== Original Assignment ==<br />
<br />
'''Do not erase this section!'''<br />
<br />
Your assignment is to create code that allows two PIC32s to communicate via I2C.<br />
<br />
== Overview ==<br />
<br />
Summarize briefly what the page is about.<br />
<br />
== Circuit ==<br />
<br />
Include a schematic and give any part numbers. A photo of your circuit is OK, but not as a replacement for a schematic.<br />
<br />
[[Image:I2C_circuit.PNG]]<br />
<br />
== Code ==<br />
<br />
(code description here)<br />
<br />
===Master Code===<br />
/*******************************************************<br />
* I2C_Master.c: Master code for I2C communication. *<br />
* Both PICS use I2C1 module to send/ recieve data.*<br />
* The master sends different values to a slave, *<br />
* which uses an interrupt to respond accordingly. *<br />
* *<br />
* Hardware: 2 PIC32MX460F512L PICs on NU32 boards *<br />
*******************************************************<br />
* Thomas Peterson, James Rein, Eric West *<br />
* ME333 Winter 2010 *<br />
* File Created: 05-FEB-2010 *<br />
* Last Modified: 14-FEB-2010 *<br />
*******************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
<br />
#define Fsck 50000 //Frequency of (I2C) serial clock<br />
<br />
#define Nop() asm( "nop" ) //No-operation; asm stands for assembly, using an assembly command in C. Cool!<br />
<br />
#define INPUT_A9 PORTAbits.RA9<br />
#define INPUT_A10 PORTAbits.RA10<br />
<br />
<br />
//function declaration for sending data and selecting slave address<br />
void SendData(int,unsigned int);<br />
void Delayms( unsigned t);<br />
<br />
/*<br />
This function is a delay function, causing the program to wait for approximately 4 * cnt cycles<br />
1 cycle is 1/SYSCLK seconds.<br />
*/<br />
void i2c_wait(unsigned int cnt)<br />
{<br />
while(--cnt)<br />
{<br />
Nop();<br />
Nop();<br />
}<br />
}<br />
<br />
<br />
/* Main function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states.<br />
SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Setup TRIS bits for switches and I2C pins<br />
TRISAbits.TRISA14=0;<br />
TRISAbits.TRISA15=0;<br />
<br />
//Initialize all of the LED pins<br />
mInitAllLEDs();<br />
mInitAllSwitches()<br />
<br />
unsigned char SlaveAddress; //Slave address variable to tell the master where to send the data. <br />
//Will be re-assigned for multiple slaves.<br />
<br />
//Enable I2C channel and set the baud rate to BRG_VAL)<br />
OpenI2C1( I2C_EN, BRG_VAL );<br />
<br />
int rcv; //For received data<br />
<br />
//While loop to test LED functionality <br />
while(1) {<br />
if (swProgram) { //First button pressed<br />
while(swProgram) { Nop(); } //Wait for release<br />
mLED_2_Toggle(); //Toggle LED2<br />
SendData(0xAA,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100);<br />
}<br />
if (swUser) { //Second button pressed<br />
while(swUser) { Nop(); }<br />
mLED_1_Toggle(); //Toggle LED1<br />
SendData(0x23,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100); <br />
}<br />
}//while loop ending<br />
<br />
return 0;<br />
} //ending main <br />
<br />
<br />
/*****************************************************<br />
* RcvData(unsigned int address) *<br />
* *<br />
* Gets a byte of data from I2C slave device at *<br />
* ADDRESS. *<br />
* *<br />
* Returns: Received data *<br />
****************************************************/<br />
int RcvData(unsigned int address) {<br />
StartI2C1(); //Send line start condition<br />
IdleI2C1(); //Wait to complete<br />
MasterWriteI2C1((address << 1) | 1); //Write out slave address OR 1 (read command)<br />
IdleI2C1(); //Wait to complete<br />
int rcv = MasterReadI2C1(); //Read in a value<br />
StopI2C1(); //Send line stop condition<br />
IdleI2C1(); //Wait co complete<br />
return rcv; //Return read value<br />
}<br />
<br />
<br />
<br />
/***************************************************<br />
* SendData(int data, unsigned int address) *<br />
* *<br />
* Sends a byte of data (DATA) over the I2C line *<br />
* to I2C address ADDRESS *<br />
* *<br />
* Returns: nothing *<br />
***************************************************/<br />
void SendData (int data, unsigned int address){<br />
StartI2C1(); //Send the Start Bit<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1((address << 1) | 0); //Sends the slave address over the I2C line. This must happen first so the <br />
//proper slave is selected to receive data.<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1(data); //Sends data byte over I2C line<br />
IdleI2C1(); //Wait to complete<br />
<br />
StopI2C1(); //Send the Stop condition<br />
IdleI2C1(); //Wait to complete<br />
<br />
} //end function<br />
<br />
<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < SYS_FREQ/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
===Slave Code===<br />
/***********************************************************************<br />
* PIC32 I2C Slave Code <br />
***********************************************************************/<br />
<br />
#include "GenericTypeDefs.h"<br />
#include "Compiler.h"<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
<br />
#define Fsck 50000<br />
#define BRG_VAL ((PBCLK/2/Fsck)-2)<br />
<br />
// this is the modules Slave Address<br />
#define SLAVE_ADDRESS 0x40<br />
<br />
// volatile variables to hold the switch and led states<br />
volatile unsigned char dataRead = 0;<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// InitI2C<br />
//<br />
// Perform initialisation of the I2C module to operate as a slave<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void InitI2C(void)<br />
{<br />
unsigned char temp;<br />
<br />
// Enable the I2C module with clock stretching enabled<br />
OpenI2C1(I2C_ON | I2C_7BIT_ADD | I2C_STR_EN, BRG_VAL);<br />
<br />
// set the address of the slave module, address matching is with bits<br />
// 7:1 of the message compared with bits 6:0 of the ADD SFR so we<br />
// need to shift the desired address 1 bit. <br />
I2C1ADD = SLAVE_ADDRESS; // >> 1;<br />
I2C1MSK = 0;<br />
<br />
// configure the interrupt priority for the I2C peripheral<br />
mI2C1SetIntPriority(I2C_INT_PRI_3 | I2C_INT_SLAVE);<br />
<br />
// clear pending interrupts and enable I2C interrupts<br />
mI2C1SClearIntFlag();<br />
EnableIntSI2C1;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// main routine<br />
// <br />
// This code example demonstrates using the PIC32 as an I2C slave<br />
// <br />
//<br />
///////////////////////////////////////////////////////////////////<br />
int main (void)<br />
{<br />
// set for 80MHz operation<br />
SYSTEMConfigPerformance(SYSCLK);<br />
// set the Pbus to be 40000000<br />
mOSCSetPBDIV(OSC_PB_DIV_2);<br />
// disable the JTAG port<br />
mJTAGPortEnable(0);<br />
// enable interrupts<br />
INTEnableSystemMultiVectoredInt();<br />
<br />
InitI2C();<br />
mInitAllLEDs();<br />
<br />
// main loop<br />
while (1) {<br />
/* If global variable "dataRead" is set high during interrupt, turn on all LEDs */<br />
if (dataRead == 0xAA)<br />
{<br />
mLED_0_On();<br />
mLED_1_On();<br />
mLED_2_On(); <br />
mLED_3_On(); <br />
<br />
}<br />
}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// Slave I2C interrupt handler<br />
// This handler is called when a qualifying I2C events occurs<br />
// this means that as well as Slave events <br />
// Master and Bus Collision events will also trigger this handler.<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void __ISR(_I2C_1_VECTOR, ipl3) _SlaveI2CHandler(void)<br />
{<br />
mLED_1_On();<br />
unsigned char temp;<br />
static unsigned int dIndex;<br />
<br />
// check for MASTER and Bus events and respond accordingly<br />
if (IFS0bits.I2C1MIF == 1) {<br />
mI2C1MClearIntFlag();<br />
return; <br />
}<br />
if (IFS0bits.I2C1BIF == 1) {<br />
mI2C1BClearIntFlag();<br />
return;<br />
}<br />
mLED_1_Off();<br />
mLED_2_On();<br />
<br />
// handle the incoming message<br />
if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 0 --> indicates last byte was address <br />
<br />
// reset any state variables needed by a message sequence <br />
// perform a dummy read of the address<br />
temp = SlaveReadI2C1();<br />
<br />
mLED_3_On();<br />
mLED_2_Off();<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release the clock<br />
<br />
} else if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
<br />
mLED_3_On();<br />
mLED_2_On();<br />
// writing data to our module, just store it in adcSample<br />
dataRead = SlaveReadI2C1();<br />
<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release clock stretch bit<br />
<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 1 --> indicates data transfer is output from slave<br />
// D/A bit = 1 --> indicates last byte was address<br />
mLED_0_On();<br />
mLED_2_Off();<br />
// read of the slave device, read the address <br />
temp = SlaveReadI2C1();<br />
dIndex = 0;<br />
SlaveWriteI2C1(dataRead);<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 1 --> indicates data transfer is input to slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
mLED_0_On();<br />
mLED_2_On();<br />
<br />
// output the data until the MASTER terminates the<br />
// transfer with a NACK, continuing reads return 0<br />
if (dIndex == 0) {<br />
SlaveWriteI2C1(dataRead);<br />
dIndex++;<br />
} else<br />
SlaveWriteI2C1(0);<br />
}<br />
<br />
// finally clear the slave interrupt flag<br />
mI2C1SClearIntFlag(); <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=PIC32MX:_I2C_Communication_between_PIC32s&diff=15901PIC32MX: I2C Communication between PIC32s2010-02-15T04:45:03Z<p>Thomas Peterson: /* Master Code */</p>
<hr />
<div>== Original Assignment ==<br />
<br />
'''Do not erase this section!'''<br />
<br />
Your assignment is to create code that allows two PIC32s to communicate via I2C.<br />
<br />
== Overview ==<br />
<br />
Summarize briefly what the page is about.<br />
<br />
== Circuit ==<br />
<br />
Include a schematic and give any part numbers. A photo of your circuit is OK, but not as a replacement for a schematic.<br />
<br />
[[Image:I2C_circuit.PNG]]<br />
<br />
== Code ==<br />
<br />
(code description here)<br />
<br />
===Master Code===<br />
/*******************************************************<br />
* I2C_Master.c: Master code for I2C communication. *<br />
* Both PICS use I2C1 module to send/ recieve data.*<br />
* The master sends different values to a slave, *<br />
* which uses an interrupt to respond accordingly. *<br />
* *<br />
* Hardware: 2 PIC32MX460F512L PICs on NU32 boards *<br />
*******************************************************<br />
* Thomas Peterson, James Rein, Eric West *<br />
* ME333 Winter 2010 *<br />
* File Created: 05-FEB-2010 *<br />
* Last Modified: 14-FEB-2010 *<br />
*******************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
<br />
#define Fsck 50000 //Frequency of (I2C) serial clock<br />
<br />
#define Nop() asm( "nop" ) //No-operation; asm stands for assembly, using an assembly command in C. Cool!<br />
<br />
#define INPUT_A9 PORTAbits.RA9<br />
#define INPUT_A10 PORTAbits.RA10<br />
<br />
<br />
//function declaration for sending data and selecting slave address<br />
void SendData(int,unsigned int);<br />
void Delayms( unsigned t);<br />
<br />
/*<br />
This function is a delay function, causing the program to wait for approximately 4 * cnt cycles<br />
1 cycle is 1/SYSCLK seconds.<br />
*/<br />
void i2c_wait(unsigned int cnt)<br />
{<br />
while(--cnt)<br />
{<br />
Nop();<br />
Nop();<br />
}<br />
}<br />
<br />
<br />
/* Main function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states.<br />
SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Setup TRIS bits for switches and I2C pins<br />
TRISAbits.TRISA14=0;<br />
TRISAbits.TRISA15=0;<br />
<br />
//Initialize all of the LED pins<br />
mInitAllLEDs();<br />
mInitAllSwitches()<br />
<br />
unsigned char SlaveAddress; //Slave address variable to tell the master where to send the data. <br />
//Will be re-assigned for multiple slaves.<br />
<br />
//Enable I2C channel and set the baud rate to BRG_VAL)<br />
OpenI2C1( I2C_EN, BRG_VAL );<br />
<br />
int rcv; //For received data<br />
<br />
//While loop to test LED functionality <br />
while(1) {<br />
if (swProgram) { //First button pressed<br />
while(INPUT_A9 == 0) { Nop(); } //Wait for release<br />
mLED_2_Toggle(); //Toggle LED2<br />
SendData(0xAA,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100);<br />
}<br />
if (swUser) { //Second button pressed<br />
while(INPUT_A10 == 0) { Nop(); }<br />
mLED_1_Toggle(); //Toggle LED1<br />
SendData(0x23,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100); <br />
}<br />
}//while loop ending<br />
<br />
return 0;<br />
} //ending main <br />
<br />
<br />
/*****************************************************<br />
* RcvData(unsigned int address) *<br />
* *<br />
* Gets a byte of data from I2C slave device at *<br />
* ADDRESS. *<br />
* *<br />
* Returns: Received data *<br />
****************************************************/<br />
int RcvData(unsigned int address) {<br />
StartI2C1(); //Send line start condition<br />
IdleI2C1(); //Wait to complete<br />
MasterWriteI2C1((address << 1) | 1); //Write out slave address OR 1 (read command)<br />
IdleI2C1(); //Wait to complete<br />
int rcv = MasterReadI2C1(); //Read in a value<br />
StopI2C1(); //Send line stop condition<br />
IdleI2C1(); //Wait co complete<br />
return rcv; //Return read value<br />
}<br />
<br />
<br />
<br />
/***************************************************<br />
* SendData(int data, unsigned int address) *<br />
* *<br />
* Sends a byte of data (DATA) over the I2C line *<br />
* to I2C address ADDRESS *<br />
* *<br />
* Returns: nothing *<br />
***************************************************/<br />
void SendData (int data, unsigned int address){<br />
StartI2C1(); //Send the Start Bit<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1((address << 1) | 0); //Sends the slave address over the I2C line. This must happen first so the <br />
//proper slave is selected to receive data.<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1(data); //Sends data byte over I2C line<br />
IdleI2C1(); //Wait to complete<br />
<br />
StopI2C1(); //Send the Stop condition<br />
IdleI2C1(); //Wait to complete<br />
<br />
} //end function<br />
<br />
<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < SYS_FREQ/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
===Slave Code===<br />
/***********************************************************************<br />
* PIC32 I2C Slave Code <br />
***********************************************************************/<br />
<br />
#include "GenericTypeDefs.h"<br />
#include "Compiler.h"<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
<br />
#define Fsck 50000<br />
#define BRG_VAL ((PBCLK/2/Fsck)-2)<br />
<br />
// this is the modules Slave Address<br />
#define SLAVE_ADDRESS 0x40<br />
<br />
// volatile variables to hold the switch and led states<br />
volatile unsigned char dataRead = 0;<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// InitI2C<br />
//<br />
// Perform initialisation of the I2C module to operate as a slave<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void InitI2C(void)<br />
{<br />
unsigned char temp;<br />
<br />
// Enable the I2C module with clock stretching enabled<br />
OpenI2C1(I2C_ON | I2C_7BIT_ADD | I2C_STR_EN, BRG_VAL);<br />
<br />
// set the address of the slave module, address matching is with bits<br />
// 7:1 of the message compared with bits 6:0 of the ADD SFR so we<br />
// need to shift the desired address 1 bit. <br />
I2C1ADD = SLAVE_ADDRESS; // >> 1;<br />
I2C1MSK = 0;<br />
<br />
// configure the interrupt priority for the I2C peripheral<br />
mI2C1SetIntPriority(I2C_INT_PRI_3 | I2C_INT_SLAVE);<br />
<br />
// clear pending interrupts and enable I2C interrupts<br />
mI2C1SClearIntFlag();<br />
EnableIntSI2C1;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// main routine<br />
// <br />
// This code example demonstrates using the PIC32 as an I2C slave<br />
// <br />
//<br />
///////////////////////////////////////////////////////////////////<br />
int main (void)<br />
{<br />
// set for 80MHz operation<br />
SYSTEMConfigPerformance(SYSCLK);<br />
// set the Pbus to be 40000000<br />
mOSCSetPBDIV(OSC_PB_DIV_2);<br />
// disable the JTAG port<br />
mJTAGPortEnable(0);<br />
// enable interrupts<br />
INTEnableSystemMultiVectoredInt();<br />
<br />
InitI2C();<br />
mInitAllLEDs();<br />
<br />
// main loop<br />
while (1) {<br />
/* If global variable "dataRead" is set high during interrupt, turn on all LEDs */<br />
if (dataRead == 0xAA)<br />
{<br />
mLED_0_On();<br />
mLED_1_On();<br />
mLED_2_On(); <br />
mLED_3_On(); <br />
<br />
}<br />
}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// Slave I2C interrupt handler<br />
// This handler is called when a qualifying I2C events occurs<br />
// this means that as well as Slave events <br />
// Master and Bus Collision events will also trigger this handler.<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void __ISR(_I2C_1_VECTOR, ipl3) _SlaveI2CHandler(void)<br />
{<br />
mLED_1_On();<br />
unsigned char temp;<br />
static unsigned int dIndex;<br />
<br />
// check for MASTER and Bus events and respond accordingly<br />
if (IFS0bits.I2C1MIF == 1) {<br />
mI2C1MClearIntFlag();<br />
return; <br />
}<br />
if (IFS0bits.I2C1BIF == 1) {<br />
mI2C1BClearIntFlag();<br />
return;<br />
}<br />
mLED_1_Off();<br />
mLED_2_On();<br />
<br />
// handle the incoming message<br />
if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 0 --> indicates last byte was address <br />
<br />
// reset any state variables needed by a message sequence <br />
// perform a dummy read of the address<br />
temp = SlaveReadI2C1();<br />
<br />
mLED_3_On();<br />
mLED_2_Off();<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release the clock<br />
<br />
} else if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
<br />
mLED_3_On();<br />
mLED_2_On();<br />
// writing data to our module, just store it in adcSample<br />
dataRead = SlaveReadI2C1();<br />
<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release clock stretch bit<br />
<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 1 --> indicates data transfer is output from slave<br />
// D/A bit = 1 --> indicates last byte was address<br />
mLED_0_On();<br />
mLED_2_Off();<br />
// read of the slave device, read the address <br />
temp = SlaveReadI2C1();<br />
dIndex = 0;<br />
SlaveWriteI2C1(dataRead);<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 1 --> indicates data transfer is input to slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
mLED_0_On();<br />
mLED_2_On();<br />
<br />
// output the data until the MASTER terminates the<br />
// transfer with a NACK, continuing reads return 0<br />
if (dIndex == 0) {<br />
SlaveWriteI2C1(dataRead);<br />
dIndex++;<br />
} else<br />
SlaveWriteI2C1(0);<br />
}<br />
<br />
// finally clear the slave interrupt flag<br />
mI2C1SClearIntFlag(); <br />
}</div>Thomas Petersonhttps://hades.mech.northwestern.edu//index.php?title=PIC32MX:_I2C_Communication_between_PIC32s&diff=15900PIC32MX: I2C Communication between PIC32s2010-02-15T04:42:16Z<p>Thomas Peterson: /* Master Code */ Stripping down code</p>
<hr />
<div>== Original Assignment ==<br />
<br />
'''Do not erase this section!'''<br />
<br />
Your assignment is to create code that allows two PIC32s to communicate via I2C.<br />
<br />
== Overview ==<br />
<br />
Summarize briefly what the page is about.<br />
<br />
== Circuit ==<br />
<br />
Include a schematic and give any part numbers. A photo of your circuit is OK, but not as a replacement for a schematic.<br />
<br />
[[Image:I2C_circuit.PNG]]<br />
<br />
== Code ==<br />
<br />
(code description here)<br />
<br />
===Master Code===<br />
/*******************************************************<br />
* I2C_Master.c: Master code for I2C communication. *<br />
* Both PICS use I2C1 module to send/ recieve data.*<br />
* The master sends different values to a slave, *<br />
* which uses an interrupt to respond accordingly. *<br />
* *<br />
* Hardware: 2 PIC32MX460F512L PICs on NU32 boards *<br />
*******************************************************<br />
* Thomas Peterson, James Rein, Eric West *<br />
* ME333 Winter 2010 *<br />
* File Created: 05-FEB-2010 *<br />
* Last Modified: 14-FEB-2010 *<br />
*******************************************************/<br />
<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
#pragma config FPBDIV = DIV_1 //Sets PBCLK to SYSCLK<br />
<br />
#define Fsck 50000 //Frequency of (I2C) serial clock<br />
<br />
#define Nop() asm( "nop" ) //No-operation; asm stands for assembly, using an assembly command in C. Cool!<br />
<br />
#define INPUT_A9 PORTAbits.RA9<br />
#define INPUT_A10 PORTAbits.RA10<br />
<br />
<br />
//function declaration for sending data and selecting slave address<br />
void SendData(int,unsigned int);<br />
void Delayms( unsigned t);<br />
<br />
/*<br />
This function is a delay function, causing the program to wait for approximately 4 * cnt cycles<br />
1 cycle is 1/SYSCLK seconds.<br />
*/<br />
void i2c_wait(unsigned int cnt)<br />
{<br />
while(--cnt)<br />
{<br />
Nop();<br />
Nop();<br />
}<br />
}<br />
<br />
<br />
/* Main function */<br />
int main(void)<br />
{<br />
// Configure the proper PB frequency and the number of wait states.<br />
SYSTEMConfigPerformance(SYS_FREQ);<br />
<br />
// Set all analog pins to be digital I/O<br />
AD1PCFG = 0xFFFF;<br />
<br />
//Setup TRIS bits for switches and I2C pins<br />
TRISAbits.TRISA9=1;<br />
TRISAbits.TRISA10=1;<br />
TRISAbits.TRISA14=0;<br />
TRISAbits.TRISA15=0;<br />
<br />
//Initialize all of the LED pins<br />
mInitAllLEDs();<br />
<br />
<br />
unsigned char SlaveAddress; //Slave address variable to tell the master where to send the data. <br />
//Will be re-assigned for multiple slaves.<br />
<br />
//Enable I2C channel and set the baud rate to BRG_VAL)<br />
OpenI2C1( I2C_EN, BRG_VAL );<br />
<br />
int rcv; //For received data<br />
<br />
//While loop to test LED functionality <br />
while(1) {<br />
if (INPUT_A9 == 0) { //First button pressed<br />
while(INPUT_A9 == 0) { Nop(); } //Wait for release<br />
mLED_2_Toggle(); //Toggle LED2<br />
SendData(0xAA,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100);<br />
}<br />
if (INPUT_A10 == 0) { //Second button pressed<br />
while(INPUT_A10 == 0) { Nop(); }<br />
mLED_1_Toggle(); //Toggle LED1<br />
SendData(0x23,0x40); //Sends hex data 0xAA to slave address 0x40<br />
rcv = RcvData(0x40); //Receives data from address 0x40 <br />
Delayms(100); <br />
}<br />
}//while loop ending<br />
<br />
return 0;<br />
} //ending main <br />
<br />
<br />
/*****************************************************<br />
* RcvData(unsigned int address) *<br />
* *<br />
* Gets a byte of data from I2C slave device at *<br />
* ADDRESS. *<br />
* *<br />
* Returns: Received data *<br />
****************************************************/<br />
int RcvData(unsigned int address) {<br />
StartI2C1(); //Send line start condition<br />
IdleI2C1(); //Wait to complete<br />
MasterWriteI2C1((address << 1) | 1); //Write out slave address OR 1 (read command)<br />
IdleI2C1(); //Wait to complete<br />
int rcv = MasterReadI2C1(); //Read in a value<br />
StopI2C1(); //Send line stop condition<br />
IdleI2C1(); //Wait co complete<br />
return rcv; //Return read value<br />
}<br />
<br />
<br />
<br />
/***************************************************<br />
* SendData(int data, unsigned int address) *<br />
* *<br />
* Sends a byte of data (DATA) over the I2C line *<br />
* to I2C address ADDRESS *<br />
* *<br />
* Returns: nothing *<br />
***************************************************/<br />
void SendData (int data, unsigned int address){<br />
StartI2C1(); //Send the Start Bit<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1((address << 1) | 0); //Sends the slave address over the I2C line. This must happen first so the <br />
//proper slave is selected to receive data.<br />
IdleI2C1(); //Wait to complete<br />
<br />
MasterWriteI2C1(data); //Sends data byte over I2C line<br />
IdleI2C1(); //Wait to complete<br />
<br />
StopI2C1(); //Send the Stop condition<br />
IdleI2C1(); //Wait to complete<br />
<br />
} //end function<br />
<br />
<br />
<br />
void Delayms( unsigned t)<br />
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ<br />
{<br />
OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);<br />
while (t--)<br />
{ // t x 1ms loop<br />
WriteTimer1(0);<br />
while (ReadTimer1() < SYS_FREQ/256/1000);<br />
}<br />
CloseTimer1();<br />
} // Delayms<br />
<br />
===Slave Code===<br />
/***********************************************************************<br />
* PIC32 I2C Slave Code <br />
***********************************************************************/<br />
<br />
#include "GenericTypeDefs.h"<br />
#include "Compiler.h"<br />
#include "HardwareProfile.h"<br />
#include <plib.h><br />
<br />
#define SYSCLK (80000000)<br />
#define PBCLK (SYSCLK)<br />
<br />
#define Fsck 50000<br />
#define BRG_VAL ((PBCLK/2/Fsck)-2)<br />
<br />
// this is the modules Slave Address<br />
#define SLAVE_ADDRESS 0x40<br />
<br />
// volatile variables to hold the switch and led states<br />
volatile unsigned char dataRead = 0;<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// InitI2C<br />
//<br />
// Perform initialisation of the I2C module to operate as a slave<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void InitI2C(void)<br />
{<br />
unsigned char temp;<br />
<br />
// Enable the I2C module with clock stretching enabled<br />
OpenI2C1(I2C_ON | I2C_7BIT_ADD | I2C_STR_EN, BRG_VAL);<br />
<br />
// set the address of the slave module, address matching is with bits<br />
// 7:1 of the message compared with bits 6:0 of the ADD SFR so we<br />
// need to shift the desired address 1 bit. <br />
I2C1ADD = SLAVE_ADDRESS; // >> 1;<br />
I2C1MSK = 0;<br />
<br />
// configure the interrupt priority for the I2C peripheral<br />
mI2C1SetIntPriority(I2C_INT_PRI_3 | I2C_INT_SLAVE);<br />
<br />
// clear pending interrupts and enable I2C interrupts<br />
mI2C1SClearIntFlag();<br />
EnableIntSI2C1;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// main routine<br />
// <br />
// This code example demonstrates using the PIC32 as an I2C slave<br />
// <br />
//<br />
///////////////////////////////////////////////////////////////////<br />
int main (void)<br />
{<br />
// set for 80MHz operation<br />
SYSTEMConfigPerformance(SYSCLK);<br />
// set the Pbus to be 40000000<br />
mOSCSetPBDIV(OSC_PB_DIV_2);<br />
// disable the JTAG port<br />
mJTAGPortEnable(0);<br />
// enable interrupts<br />
INTEnableSystemMultiVectoredInt();<br />
<br />
InitI2C();<br />
mInitAllLEDs();<br />
<br />
// main loop<br />
while (1) {<br />
/* If global variable "dataRead" is set high during interrupt, turn on all LEDs */<br />
if (dataRead == 0xAA)<br />
{<br />
mLED_0_On();<br />
mLED_1_On();<br />
mLED_2_On(); <br />
mLED_3_On(); <br />
<br />
}<br />
}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////<br />
//<br />
// Slave I2C interrupt handler<br />
// This handler is called when a qualifying I2C events occurs<br />
// this means that as well as Slave events <br />
// Master and Bus Collision events will also trigger this handler.<br />
//<br />
///////////////////////////////////////////////////////////////////<br />
void __ISR(_I2C_1_VECTOR, ipl3) _SlaveI2CHandler(void)<br />
{<br />
mLED_1_On();<br />
unsigned char temp;<br />
static unsigned int dIndex;<br />
<br />
// check for MASTER and Bus events and respond accordingly<br />
if (IFS0bits.I2C1MIF == 1) {<br />
mI2C1MClearIntFlag();<br />
return; <br />
}<br />
if (IFS0bits.I2C1BIF == 1) {<br />
mI2C1BClearIntFlag();<br />
return;<br />
}<br />
mLED_1_Off();<br />
mLED_2_On();<br />
<br />
// handle the incoming message<br />
if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 0 --> indicates last byte was address <br />
<br />
// reset any state variables needed by a message sequence <br />
// perform a dummy read of the address<br />
temp = SlaveReadI2C1();<br />
<br />
mLED_3_On();<br />
mLED_2_Off();<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release the clock<br />
<br />
} else if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 0 --> indicates data transfer is input to slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
<br />
mLED_3_On();<br />
mLED_2_On();<br />
// writing data to our module, just store it in adcSample<br />
dataRead = SlaveReadI2C1();<br />
<br />
// release the clock to restart I2C<br />
I2C1CONbits.SCLREL = 1; // release clock stretch bit<br />
<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 0)) {<br />
// R/W bit = 1 --> indicates data transfer is output from slave<br />
// D/A bit = 1 --> indicates last byte was address<br />
mLED_0_On();<br />
mLED_2_Off();<br />
// read of the slave device, read the address <br />
temp = SlaveReadI2C1();<br />
dIndex = 0;<br />
SlaveWriteI2C1(dataRead);<br />
} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 1)) {<br />
// R/W bit = 1 --> indicates data transfer is input to slave<br />
// D/A bit = 1 --> indicates last byte was data<br />
mLED_0_On();<br />
mLED_2_On();<br />
<br />
// output the data until the MASTER terminates the<br />
// transfer with a NACK, continuing reads return 0<br />
if (dIndex == 0) {<br />
SlaveWriteI2C1(dataRead);<br />
dIndex++;<br />
} else<br />
SlaveWriteI2C1(0);<br />
}<br />
<br />
// finally clear the slave interrupt flag<br />
mI2C1SClearIntFlag(); <br />
}</div>Thomas Peterson