Código Acelerómetro para Codewarrior 10.6
A continuación se presenta el código para el funcionamiento del Acelérometro MMA7660SC, implementado en el módulo DEMOQE128.
Originalmente este código se diseñó para ser compilado en una versión de codewarrior más antigua que la versión 10.6. Por lo que se hizo el trabajo de mudar el programa y sus directivas a esta nueva versión.
A lo largo de esta sección se explicarán todas las funciones que son implementadas para ejecutar el acelerómetro, conjunto con esto se especifica cuáles fueron los módulos y registros utilizados para la implementación de este programa.
El hecho de entender el funcionamiento del acelerómetro en este módulo permite ampliar los conocimientos obtenidos para su uso en otras aplicaciones como:
•Teléfono móvil / PMP / PDA: Orientación de detección (vertical / horizontal),estabilidad de imagen, texto de desplazamiento, movimiento de marcar.
• PC portátil: antirrobo.
• Juegos: detección de movimiento, Auto-Enciende / Apaga para baja potencia Consumo.
• Cámara fotográfica digital: estabilidad de la imagen
Contenido
- 1 Acelerómetro MMA7660FC
- 2 Descripción de funciones
- 3 Material de Referencia
Acelerómetro MMA7660FC
El MMA7660FC es un output digital I²C, de muy bajo consumo de energía, de perfil capacitivo micro-mecanizado con un filtro de paso bajo, con compensación offset para gravedad cero, ganancia de errores y conversión a valores digitales de seis bits van en una salida de datos configurable por el usuario. El dispositivo puede ser utilizado para cambios de datos del sensor, la orientación del producto y la detección de gesticulaciones a través de un pin de interrupción (INT). El dispositivo es de 3 mm x 3 mm x 0,9 mm encapsulado en un DFN (plano, dual y libre de plomo).
Información técnica
Modos de Opercaión y tipos de Detecciones
•Modos de Operación:
•Modo sin Corriente
•Modo Apagado
•Modo en Espera
•Modo Activo
• Tipos de detecciones:
•Detección de Orientación
•Detección de Golpe
•Detección de Agite
•Auto-Encendido/Apagado
Interfaz Serial
-Direccionamiento Serial.
El MMA7660FC funciona como un esclavo que envía y recibe datos a través de una interfaz de 2 hilos I²C. La interfaz utiliza una Serie de Línea de Datos (SDA) y una Línea de Reloj en Serie (SCL) para lograr una comunicación bidireccional entre el maestro (M) y el esclavo (S). Un maestro (típicamente un microcontrolador) inicia todas las transferencias de datos hacia y desde el dispositivo, y genera el reloj SCL que sincroniza la transferencia de datos.
Formato de mensaje de Escritura
Formato de mensaje de lectura
Descripción de funciones
Main del programa
A continuación se presenta el código del main, en el cual se invocan las funciones que fueron declaradas para el funcionamiento del acelerómetro.
void main(void){
PeriphInit();
InitKBI();
IIC_configuration();
EnableInterrupts;
MMA7660_configuration();
// Selects fBUS as timer1 clock source and start timer
TPM1SC = 0x08;
// SendMsg("\fX, Y, Z\r\n");
while (!SW1){}
for(;;){
while(SW4){
if(!(IIC_Rec_Data[0]&IIC_Rec_Data[1]&IIC_Rec_Data[2]&0x40)){
ShowAcceleration(); // Show acceleration data
}else{ // MMA7660 updating
}
if (PTHD_PTHD7 == 1) { //Wait for IIC bus to be free
while (PTHD_PTHD7 == 0); // Wait while pin is low
while (IIC2C1_MST == 1); // Wait untill IIC is stopped
//Read Xout, Yout, Zout
mma7660[0] = 0x98;
mma7660[1] = 0x00;
Master_Read_MMA7660_register(2,3);
}else {
}
} //end while(SW4)
while(SW3){_Stop;}
} //end for(;;)
}
Inicialización de periféricos
ICS
El módulo ICS (Internal Clock Source) maneja las diferentes opciones para la fuente de reloj. Tiene siete modos de operación: FEI, FEE, FBI, FBILP, FBE, FBELP, y stop. En este caso se trabajará con el modo por defecto FEI.
Para inicilizar el ICS deben modificarse tres de sus registros:
ICSC1: ICS Control Register 1
ICSC2: ICS Control Register 2
ICSSC: ICS Status and Control
Rutina de inicialización
Se habilitará el bit correspondiente a ICSC1IREFS, con el cual se hace referencia al reloj interno. Además, con este rutina de inicialización los bits correspondientes a ICSC2EREFS e ICSC2ERCLKEN se habilitan, donde el primero permite seleccionar la fuente del reloj externa, solicitando al oscilador. El segundo, nos permite seleccionar la referencia al reloj externo que será usado como ICSERCLK Por último, se habilitan los bits correspondientes a ICSSCLKST con los cuales seleccionamos la referencia externa del reloj con el modo FLL Bypassed.
#Define ICSC1_FEI 0x04
#Define ICSC2_FEI 0x06
#Define ICSSC_FEI 0x80
void ICS_FEI(void) {
if (NVICSTRM != 0xFF)
ICSTRM = NVICSTRM;
else
ICSTRM = 0xAD;
ICSC1 = ICSC1_FEI;
ICSC2 = ICSC2_FEI;
ICSSC = ICSSC_FEI;
while (ICSC1_CLKS != ICSSC_CLKST) {}
}
KBI
Es el módulo de interrupción por teclado.
El registro KBIxSC es un registro de estado y control.Está compuesto por 8 bits, de los cuales, los 4 bits menos significativos tienen un significado específico y permiten habilitar y deshabilitar distintas instancias de las interrupciones y manejar el control del registro.
El registro KBIxPE es un registro que está relacionado con los pines de la tarjeta de desarrollo. Nos permite configurar cuáles pines se habilitarán para las interrupciones, los cuales posteriormente serán enlazados con algún dispositivo.
Rutina de Inicialización
Se deben configurar los bits correspondientes al registro KBI1PE para habilitar las interrupciones de los pines que son necesarios para el programa.En este caso se habilitan los bits correspondientes a KBI1P2 y KBI1P3 con el objetivo de habilitar las interrupciones de los pines PTA2 y PTA3 de la tarjeta de desarrollo. Por otro lado, se habilita el bit 1 correspondiente al registro KBI1SC con el objetivo de habilitar las solicitudes de interrupciones. Además se procede a habilitar el bit 2 como mecanismo de limpieza de la bandera asociada al registro.
#Define KBI_SW KBI1PE_KBIPE2_MASK | KBI1PE_KBIPE3_MASK
void InitKBI(void) {
// Enable KBI1P[3:2] as interrupt
KBI1PE = KBI_SW;
KBI1SC = 0b00000110;
/* ||||
|||+---- KBIMOD = KBI detection mode: 0=edge only
||+----- KBIE = KBI int enable: 1=enabled
|+------ KBACK = KBI int acknowledge: 1=clr IRQF
+------- KBF = KBI flag
*/
}
SCI
SCI (Serial Communications Interface), permite la transmisión y recepción de datos por el puerto serial. La configuración de este módulo es necesaria, ya que a través de su registro de datos (SCIxD) se lleva a cabo la transmisión de la data desde el acelerómetro al procesador. En este caso, para su inicialización solo hace falta modificar dos registros:
SCIxBDL
SCIxBDH
void InitSCI(word baud) {
SCI1BD = baud; // Se configura el valor de la tasa de baudios especificada
}
PeriphInit
Es quien se encarga de desabilitar interrupciones, habilito todos los pullups (Desde PTA hasta PTJ), iniciar el módulo SCI y además configurar ciertos parámetros del acelerómetro.
void PeriphInit(void)
{
// Desabilita las instrucciones de COP y habilita las instrucciones de STOP, RESET y BKGD
SOPT1 = 0x23;
// Selecciona el modo FEI
// Seteamos el trimming del fBUS en 25 MHz
ICS_FEI();
// Enable all pullups
PTAPE = 0xFF;
PTBPE = 0xFF;
PTCPE = 0xFF;
PTDPE = 0xFF;
PTEPE = 0xFF;
PTFPE = 0xFF;
PTGPE = 0xFF;
PTHPE = 0xFF;
PTJPE = 0xFF;
/* Configura PTG[2:1] como la sensitividad del acelerometro
PTG2:PTG1
0 0 = 1.5g
0 1 = 2.0g
1 0 = 4.0g
1 1 = 6.0g
*/ //
PTGD = 0x00; // selecciono como salida el puerto G
PTGDD = 0x06; // selecciono sensibilidad de 1.5g
// Timer2 overflow about every 1ms
TPM2MOD = 25000; // registro que contiene el valor del contador
// Detiene el Timer2 y selecciona 1 como un divisor prescalar
TPM2SC = 0x00; //
// Initializes SCI Peripheral
InitSCI(fei_baud);
}
IIC
La función IIC_Configuration se encarga de setear los bits de dos registros para configurar dos cosas principalmente:
El Registro de Divisor de Frecuencia, para definir la taza de baudios y el tiempo de espera.
El Registro de Control del IIC, configurando La habilitación del modulo IIC, sus interrupciones y definiéndolo en modo esclavo, receptor y con una señal de acknowledge.
Rutina de inicialización
Se inicializa el bit correspondiente a IIC2CI-IICEN para habilitar el modulo de IIC y además se inicializa el bit correspondiente a IIC2CI-IICIE con el objetivo de habilitar las solicitudes de interrrupción
void IIC_configuration (void) {
IIC2F = 0x90;
IIC2C1 = 0xC0;
}
Funciones para la transmisión y recepción de datos
La función RecChar obtiene uno a unos los caracteres enviados a tráves del protocolo IIC desde el registro SCI1D. De igual forma, las funciones SendChar y SendMsg utilizan este registro, descomponen en caracteres el mensaje y lo envían uno a uno.
Los registros utilizados por estas funciones son:
Se utiliza para habiliar la transmisión o la recepción. SCIxC2: SCI Control Register 2
Se utiliza para saber si se han enviado o leído datos desde el módulo. SCIxS1: SCI Status Register 1
Se utiliza para la recepción y transmisión de datos.
SCIxD: SCI Data Register
Recibir caracter
char RecChar(void) {
byte rec_char;
if (SCI1S1_RDRF) // Si el buffer de transmisión esta lleno
rec_char = SCI1D; // Limpio el buffer
SCI1C2_RE = 1; //Habilito la transmisión
while(!SCI1S1_RDRF){ };// Espero hasta que el buffer no esté vacío
rec_char = SCI1D; // Obtengo el caracter enviado
SendChar((char) rec_char);// Reenvío el caracter
return (char) SCI1D;
}
Enviar caracter
void SendChar(char s_char) {
SCI1C2 = 0x08; // Habilito la transmisión de datos
while(!SCI1S1_TDRE){ } //Mientras el buffer de transmisión de datos este lleno espero
SCI1D = (byte) s_char; //Escribe el caracter que será enviado
}
Enviar mensaje
void SendMsg(char msg[]) {
byte i=0;
char nxt_char;
SCI1C2 = 0x08; //Habilito la transmisión
nxt_char = msg[i++];
while(nxt_char != 0x00) { //Mientras sea diferente de NULL
while(!SCI1S1_TDRE){} //Mientras el buffer de transmsión de datos este lleno espero
SCI1D = (byte) nxt_char; // Escribo el caracter en el registro de datos
nxt_char = msg[i++]; //Leo el siguiente caracter del arreglo
}
}
Conversión de datos
Las funciones definidas a continuación tienen el objetivo de hacer la conversión de los datos para el funcionamiento correcto del acelerómetro.
Hexadecimal a BCD (Binary-Coded Decimal)
word hex2bcd(word hex){
byte dec[4],i;
word bcd;
for (i=0;i<4;i++){
dec[i] = (byte) (hex%10);
hex = (word) (hex/10);
}
if (hex>0){
bcd=0xffff;
}else{
bcd=(word)((word)(dec[3]<<12) + (word)(dec[2]<<8) + (dec[1]<<4) + dec[0]);
}
return bcd;
}
De ASCII a un byte
byte asc2byte(char n_asc) {
byte n;
n = (byte)(n_asc - 0x30); //Conviere de ASCII a int
if(n > 0x09) // if num is $a or larger...
n -= 0x07; // ...sub $7 to correct
if(n > 0x0f) // if lower case was used...
n -= 0x20; // ...sub $20 to correct
if(n > 0x0f) // if non-numeric character...
n = 0x00; // ...default to '0'
return n;
}
De ASCII a una palabra
word asc2word(byte n_asc[2]) {
word n,n2;
// assumes n_asc[0] is MSB, n_asc[1] is LSB
n = (word)(n_asc[0] - 0x30); //convert from ascii to int
if(n > 0x09) // if num is $a or larger...
n -= 0x07; // ...sub $7 to correct
if(n > 0x0f) // if lower case was used...
n -= 0x20; // ...sub $20 to correct
if(n > 0x0f) // if non-numeric character...
n = 0x00; // ...default to '0'
n = (word)(n<<8); // shift into high byte
n2 = (word)(n_asc[1] - 0x30); //convert from ascii to int
if(n2 > 0x09) // if num is $a or larger...
n2 -= 0x07; // ...sub $7 to correct
if(n2 > 0x0f) // if lower case was used...
n2 -= 0x20; // ...sub $20 to correct
if(n2 > 0x0f) // if non-numeric character...
n2 = 0x00; // ...default to '0'
n += n2; //
return n;
}
De byte a ASCII
char * byte2asc(byte num, byte base) {
byte n;
if (base){
n=(byte)(hex2bcd(num));
}else{
n=num;
} //end if (base)
n_str[0] = (byte)((n>>0x04)+0x30); // convert MSN to ascii
if(n_str[0]>0x39) // if MSN is $a or larger...
n_str[0]+=0x07; // ...add $7 to correct
n_str[1] = (byte)((n&0x0f)+0x30); // convert LSN to ascii
if(n_str[1]>0x39) // if LSN is $a or larger...
n_str[1]+=0x07; // ...add $7 to correct
n_str[2] = 0x00; // add line feed
return (char *) n_str;
} //end byte2asc
De palabra a ASCII
char * word2asc(word num, byte base) {
word n;
if (base){
n=hex2bcd(num);
}else{
n=num;
} //end if (base)
n_str[0] = (byte)((n>>12)+0x30); // convert MSN to ascii
if(n_str[0]>0x39) // if MSN is $a or larger...
n_str[0]+=0x07; // ...add $7 to correct
n_str[1] = (byte)(((n>>8)&0x0f)+0x30); // convert 2nd MSN to ascii
if(n_str[1]>0x39) // if LSN is $a or larger...
n_str[1]+=0x07; // ...add $7 to correct
n_str[2] = (byte)(((n>>4)&0x0f)+0x30); // convert 2nd MSN to ascii
if(n_str[2]>0x39) // if LSN is $a or larger...
n_str[2]+=0x07; // ...add $7 to correct
n_str[3] = (byte)((n&0x0f)+0x30); // convert 2nd MSN to ascii
if(n_str[3]>0x39) // if LSN is $a or larger...
n_str[3]+=0x07; // ...add $7 to correct
n_str[4] = 0x00; // add line feed
return (char *) n_str;
} //end word2asc
Aceleración
Read Acceleration
void ReadAcceleration(void){
byte i;
signed int temp;
for(i=0;i<3;i++){
temp = IIC_Rec_Data[i] & 0x3F;
if(IIC_Rec_Data[i] & 0x20){
temp |= 0xFFC0;
temp += 32;
IIC_Converted_Data[i] = temp;
}else{
IIC_Converted_Data[i] = temp + 32;
}
}
}
Show Acceleration
Esta función de encarga de definir el modo en el que el acelerómetro trabajará (Para filtrar data, promediar data o copiar la data) que lee de acuerdo a los movimientos recibidos por el IIC. (Es asignado de recuerdo a la variable IIC_Converted_Data).
Posteriomente, se encarga de enviarle al "maestro" el mensaje con las coordenadas de la posición leída mediante el uso de la función SengMsg() (carácter por carácter leído) y, ya que se está trabajando con buffers de espacio limitado por un 'max', en caso de que se llenen porque han leído muchos datos, estos son re-escritos.
void ShowAcceleration (void)
{
word SampleCNT;
byte j,k;
ReadAcceleration();
ADCSC1 = 0x01;
x.reading[samp] = (dword)( IIC_Converted_Data[0] <<8);
ADCSC1 = 0x08;
y.reading[samp] = (dword)( IIC_Converted_Data[1] <<8);
ADCSC1 = 0x09;
z.reading[samp] = (dword)( IIC_Converted_Data[2] <<8);
StartTPM(0); //0 = TPM prescaler = /2
if(samp>0){
switch (mode){
case filter: filter_data(); break;
case avg : avg_data(); break;
default : copy_data();
}
} else {
copy_data();
}
SampleCNT = StopTPM();
if (SampleCNT<0x0100) {
for(j=0xff;j>0;j--){
for(k=0x10;k>0;k--){}
}
}
// Display Acceleration
SendMsg("\r\n");
SendMsg(word2asc((word)x.result[samp],dis_base));
SendMsg(",");
SendMsg(word2asc((word)y.result[samp],dis_base));
SendMsg(",");
SendMsg(word2asc((word)z.result[samp],dis_base));
SendMsg(",");
SendMsg(word2asc(SampleCNT,dis_base));
// Cambio el arreglo de los resultados si se llega al máximo
if (samp >= max-1) {
for (j=0;j<max-1;j++){
x.result[j] = x.result[j+1];
x.reading[j] = x.reading[j+1];
y.result[j] = y.result[j+1];
y.reading[j] = y.reading[j+1];
z.result[j] = z.result[j+1];
z.reading[j] = z.reading[j+1];
}
samp = max-1;
} else {
samp++;
}
}
Configuracion del MMA7660fC
El MMA7660 usa la configuracion por defecto. 120 samples/second, se desabilitan las interrupciones. Entra en modo activo.
void MMA7660_configuration(void){
mma7660[0] = 0x98;
mma7660[1] = 0x07;
mma7660[2] = 0x01;
Master_Write_MMA7660_register(3);
}
Funciones del Maestro
Master_Read_and_Store
Se define esta función, realizada por el maestro, que tiene el objetivo de tomar el contenido del registro de datos del IIC (IIC2D) y almacenarlo en la variable IIC_Rec_Data[] para su posterior uso.
void Master_Read_and_Store(void) {
IIC_Rec_Data[rec_count++] = IIC2D;
}
void Master_Write_MMA7660_register(byte transbytes) {
last_byte = 0; // Initialize variables to 0
count = 0;
bytes_to_trans = transbytes;
if (transbytes == 0) return;
IIC2C1_TX = 1; // Set TX bit for Address cycle
IIC2C1_MST = 1; // Set Master Bit to generate a Start
IIC2D = mma7660[count++]; // Send first byte (should be 7-bit address + R/W bit)
}
void Master_Read_MMA7660_register(byte transbytes, byte recbytes) {
rec_count = 0; // Initialize variables to 0
last_byte = 0;
count = 0;
repeat_start_sent = 0;
bytes_to_trans = transbytes;
num_to_rec = recbytes;
if (transbytes == 0) return;
IIC2C1_TXAK = 0;
IIC2C1_TX = 1; // Set TX bit for Address cycle
IIC2C1_MST = 1; // Set Master Bit to generate a Start
reading_mma7660_reg = 1;
IIC2D = mma7660[count++]; // Send first byte (should be 7-bit address + R/W bit)
}
Configuración del TMP
Modulo Periférico Timer/PWM
-- Cuando el MCU entra en el modo de parada, el reloj de los módulos TPM1 y TPM2 se para . los módulos de media
operación . Si la MCU está configurado para entrar en el modo de stop2 o stop1 , los módulos TPM se restablecerán
al despertar de la parada y deben ser reinstaladas .
void StartTPM(byte PS){
TPM1SC = (byte)(0x08 | (0x07&PS));
StartCount = TPM1CNT;
}
word StopTPM(void){
StopCount = (word)(TPM1CNT - StartCount);
TPM1SC = 0;
return StopCount;
}
Rutinas de Interrupción
KBI_ISR
El modulo KBI en se puede considerar como el modulo IRQ extendido. La diferencia entre estos módulos es que el modulo KBI es sensible a varios pines, y el modulo IRQ es sensible a un solo pin. La interrupción del modulo IRQ tiene mayor prioridad que la del modulo KBI. El modulo KBI no necesita ser habilitado desde CONFIG .
En el modulo KBI los modos también son por nivel y/o flanco. Se puede configurar la polaridad en cada pin KBI para que ingrese ya sea por nivel alto y/o flanco de subida o por nivel bajo y/o flanco de bajada. Ademas se puede configurar si un pin queda como pin de propósito general o como pin KBI.
''interrupt VectorNumber_Vkeyboard ''
#Define KBI_VAL (PTAD&0x0C)>>2
void KBI_ISR(void){
byte d,b;
//capture which pin was pushed
mode = (byte)(KBI_VAL);
//debounce button
for (d=0xff;d>0;d--){
for (b=0x80;b>0;b--){}
}
//clear KBF
KBI1SC_KBACK = 1;
}
IIC_ISR
''interrupt VectorNumber_Viicx''
el IIC se puede usar con operaciones donde funge de esclavo, Se enviara una señal que sera la encargada de iniciar la comunicación .
Si se implementa como como maestro inicializaremos la comunicación desde el registro IICD.
void IIC_ISR(void) {
IIC2S_IICIF = 1; // Clear Interrupt Flag
if (IIC2C1_TX) { // Transmit or Receive?
///////////////////// Transmit ////////////////////////////
if (repeat_start_sent) {
IIC2C1_TX = 0; // Switch to RX mode
if (num_to_rec == 1)
IIC2C1_TXAK = 1; // This sets up a NACK
IIC2D; // Dummy read from Data Register
}
else if ((last_byte) & (reading_mma7660_reg)) {
IIC2C1_RSTA = 1; //Repeat start
IIC2D = (mma7660[0] | 0x01); //Set Read bit
repeat_start_sent = 1;
}
else if (last_byte) { // Is the Last Byte?
IIC2C1_MST = 0; // Generate a Stop
}
else if (last_byte != 1) {
if (IIC2S_RXAK) { // Check for ACK
IIC2C1_MST = 0; // No ACk Generate a Stop
}
else if (!IIC2S_RXAK) {
IIC2D = mma7660[count++]; // Transmit Data
if (count == bytes_to_trans)
last_byte = 1;
}
}
} else {
///////////////////// Receive ////////////////////////////
if ((num_to_rec - rec_count) == 2) {
IIC2C1_TXAK = 1; // This sets up a NACK
Master_Read_and_Store();
}
else if ((num_to_rec - rec_count) == 1) {
IIC2C1_MST = 0; // Send STOP
Master_Read_and_Store();
}
else {
Master_Read_and_Store();
}
}
}
Modos de Operación
Por medio de interrupciones, se puede detectar el modo en el que el acelerómetro trabajará. Cada uno de estos modos se encarga de almacenar el valor que recibe cuando el sensor detecta un cambio en las coordenadas de X, Y, Z y puede Filtrar los datos de los valores recibidos (Función filter_data) Promedia el valor de las lecturas realizadas (Función Avg_data) o simplemente copiar los valores de la lectura de las variables (Función copy_data).
Filter_data
void filter_data(void)
{ // guarda el valor de las coordenadas de X,Y,Z
byte i;
dword X, Y, Z;
X = x.reading[samp];
Y = y.reading[samp];
Z = z.reading[samp];
for (i=samp;i>0;i--){
X = (X + ((x.reading[i] + x.result[i-1])>>1))>>1;
Y = (Y + ((y.reading[i] + y.result[i-1])>>1))>>1;
Z = (Z + ((z.reading[i] + z.result[i-1])>>1))>>1;
}
x.result[samp] = (word)X;
y.result[samp] = (word)Y;
z.result[samp] = (word)Z;
}
Avg_data
void avg_data(void)
{// promedia el valor leido por x, y, z desde j hasta samp
byte j;
long x_avg=0, y_avg=0, z_avg=0;
for (j=1;j<=samp;j++){
x_avg += x.reading[j];
y_avg += y.reading[j];
z_avg += z.reading[j];
}
x.result[samp] = (word)(x_avg>>4);
y.result[samp] = (word)(y_avg>>4);
z.result[samp] = (word)(z_avg>>4);
}
Copy_data
void copy_data(void) { // copio el promedio leido en una variable resultado para x, y ,z
x.result[samp] = x.reading[samp];
y.result[samp] = y.reading[samp];
z.result[samp] = z.reading[samp];
}
Material de Referencia
Código del proyecto: