Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Balkonkraftwerk Speicher und Wechselrichter Tests und Tutorials

Der RP6

Allgemein

In diesem Artikel geht es um die Programmierung des RP6 und seiner Erweiterungsplatinen RP6 CONTROL M32 und RP6 CCPRO M128. Zu den Grundlagen des RP6 gibt es eine eigene Seite: RP6


RP6Loader

Der RP6Loader ist hier zu finden.

Loader Versionen

Hier eine Tabelle der (mir) bekannten RP6Loader Versionen für die RP6 Base und M32:

ZIP-Datum Version Bemerkungen Examples
03.08.2007 1.1c ab Win2k SP4 (XP, VISTA, W7)
05.09.2007 1.1e - JRE1.5 JRE5, Version für Win98SE/ME
07.09.2007 1.1c wie 03.08.2007 (?)
28.09.2007 1.2 ab 1.2: \n wird gesendet ab 16.10.2007
30.09.2007 1.2 wie 28.09.2007 (?)
17.12.2007 1.4 - BETA ab 1.4: neuer Encodertest neuer Selftest!
23.12.2007 1.4 - BETA spezielle Testversion
28.03.2008 1.4c diese Version gibt's auch für Linux 64bit

Projekte

RP6 Base und CONTROL M32

Der RP6 und die M32 können frei in C programmiert werden. Dies wird durch die umfangreiche Funktionsbibliothek und die detailliert beschriebene Anleitung auch Anfängern sehr leicht gemacht. Die Software, die zur Programmierung verwendet wird, ist ausschließlich Freeware und kann entweder der CD entnommen oder aus dem Internet (hier) heruntergeladen werden.

Demo-Programme

Die RP6 Base und M32 Demo Programme sind hier zu finden.

RP6 Base

Ein Programm für die RP6 Base kann zum Beispiel so aussehen:


#include "RP6RobotBaseLib.h" // IMMER einbinden	

int main(void)
{
	initRobotBase(); // Den Roboter initialisieren

	setLEDs(0b111111); // Alle LEDs anschalten
	moveAtSpeed(100,100); // Mit Geschwindigkeit 100 auf beiden Motoren fahren
	mSleep(2000); // 2 Sekunden warten
	Stop(); // Anhalten

	while(true)
	{
               task_motionControl; // Geschwindigkeit regeln
	}
	return 0; 

}

In dem Programm würde der RP6 als erstes alle LEDs anschalten und für 2 Sekunden mit der selben Geschwindigkeit auf beiden Motoren fahren und dann stehen bleiben.

CONTROL M32

Noch ein Beispiel für die M32:


// Includes:

#include "RP6ControlLib.h" 		// IMMER einbinden!!!


int main(void)
{
	initRP6Control(); // IMMER als ERSTES aufrufen!!!
	initLCD(); // Das LCD starten. Muss IMMER aufgerufen werden, BEVOR das LCD verwendet wird!
	setLEDs(0b1111); // Alle LEDs ein
	mSleep(500); // Eine halbe Sekunde warten
	setLEDs(0b0000); // Alle LEDs aus
	sound(180,80,25); // 2 mal Piepsen
	sound(220,80,0);
	showScreenLCD("################", "################"); // Etwas auf dem Display zeigen
	mSleep(1500); // Warten
	showScreenLCD("<<RP6  Control>>", "<<LC - DISPLAY>>"); //
	mSleep(2500); // Warten
	showScreenLCD("Hello World", "Example Program");
	mSleep(2500); //Warten
	clearLCD(); // Das LCD löschen
		
	while(true) 
	{
	     mSleep(1500); // Ewig warten...
	}
	return 0;
}


Library

Versionen

Hier eine Tabelle der (mir) bekannten Library Versionen der RP6 Base und M32:

ZIP-Datum VERSION_ RP6 RP6Control RP6LIB_VERSION RP6Config.h BaseLib ControlLib UartLib MasterTWI SlaveTWI
07.06.2007 1.0 1.0 1.0 nein 1.0_16.05.07 1.0_10.04.07 1.0_16.05.07 1.0_10.04.07 1.0_16.05.07 1.0_16.05.07
08.07.2007 1.0 1.0 1.0 nein 1.0_16.05.07 1.0_10.04.07 1.0_16.05.07 1.0_10.04.07 1.0_16.05.07 1.0_16.05.07
31.07.2007 1.1 1.1 1.0 nein 1.1_27.07.07 1.1_27.07.07 1.0_16.05.07 1.0_10.04.07 1.0_16.05.07 1.0_16.05.07
07.08.2007 1.2 1.2_07.08.07 1.0 nein 1.2_07.08.07 1.2_07.08.07 1.0_16.05.07 1.0_10.04.07 1.0_16.05.07 1.0_16.05.07
11.08.2007 1.2 1.2_07.08.07 1.0 nein 1.2_07.08.07 1.2_07.08.07 1.0_16.05.07 1.0_10.04.07 1.0_16.05.07 1.0_16.05.07
28.09.2007 1.3 1.3_25.09.07 1.1 nein 1.2_07.08.07 1.3_25.09.07 1.1 1.1_10.09.07 1.0_16.05.07 1.0_16.05.07
16.10.2007 1.3 1.3_25.09.07 1.1 13 1.2_07.08.07 1.3_25.09.07 1.1 1.1_10.09.07 1.0_16.05.07 1.0_16.05.07
10.05.2008 1.4 1.4_29.04.08 1.1 13 1.2_07.08.07 1.4_29.04.08 1.1 1.1_10.09.07 1.0_16.05.07 1.0_16.05.07
15.09.2008 1.5 1.5_12.09.08 1.2 13 1.2_07.08.07 1.4_29.04.08 1.1 1.1_10.09.07 1.0_16.05.07 1.0_16.05.07
13.03.2010 1.5 1.5_12.09.08 1.3beta 15 1.3beta

In der 1. Spalte findet ihr das Datum der RP6Examples.zip Datei, in der die Library enthalten ist. Die 2. Spalte nennt die Version, die im Dateinamen der VERSION_x.x.txt Datei als x.x vorkommt. In der 3. und 4. Spalte steht die Versionsangabe der RP6Library und RP6ControlLibrary laut Angabe in der VERSION_x.x.txt Datei.

In der 5. Spalte gebe ich den Wert der Konstante RP6LIB_VERSION an. Es gibt sie erst ab den Examples vom 16.10.2007. In den Spalten 6 bis 11 führe ich nacheinander die Versionsnummern und ggf. das in der Datei genannte Datum der Header-Datei/Library an: RP6Config.h, BaseLib, ControlLib, UartLib, MasterTWI, SlaveTWI.

Die jeweils aktuelle Library ist in den Demo-Programmen auf der AREXX Homepage enthalten. Link siehe oben! Die RP6Control Library in der Version 1.3beta vom 13.03.2010 könnt ihr hier finden.

RP6RobotBase Library

Konfiguration

Die wesentlichen Hardware-Konfigurationen des RP6 sind in der "RP6Config.h" zu finden. Sie enthält Festlegungen zu:

  • Encoder-Auflösung
  • Rotations-Faktor
  • Geschwindigkeits-Messintervall
  • Power On Warnung
  • ACS

Diese Header-Datei wird standardmäßig auch in die RP6RobotBase Library eingebunden.

Port-Verwendung

Die Verwendung der Ports des RP6 Microcontrollers wird festgelegt in der Header-Datei "RP6RobotBase.h". In dieser Datei wird auch noch Folgendes aufgeführt:

  • Quarzfrequenz (F_CPU)
  • True/false Definition
  • Verschiedene Macros
  • Baudrate der seriellen Schnittstelle

Hier eine Tabelle mit den Port-Definitionen für die RP6 Base:

Port Name In/Out Pullup Wert Funktion Bezeichnung Stecker Anmerkungen
PA0 ADC0 In 0 ADC0 VDD/GND/ADC0 ADC_ADC0 (frei)
PA1 ADC1 In 0 ADC1 VDD/GND/ADC1 ADC_ADC1 (frei)
PA2 ADC2 In 0 LS_R ADC_LS_R
PA3 ADC3 In 0 LS_L ADC_LS_L
PA4 ADC4 In/(Out) 0 E_INT1 (INT1) XBUS: 8 XBUS INT1
PA5 ADC5 In 0 MCURRENT_R * ADC_MCURRENT_R
PA6 ADC6 In 0 MCURRENT_L * ADC_MCURRENT_L
PA7 ADC7 In 0 UBAT * ADC_BAT
PB0 T0/XCK Out 0 SL6 Status LED 6
PB1 T1 Out 0 SL5 Status LED 5 ***
PB2 AIN0/INT2 In 0 ACS (INT2) IR Empfänger (TSOP)
PB3 AIN1/OC0 Out 0 ACS_PWRH ACS Sendedioden HiPwr
PB4 SS Out 0 PWRON Power On **
PB5 MOSI In 0 ISP START ISP: 1 Start/Stop-Taster
PB6 MISO Out 0 ISP ACS_L ISP: 9 ACS Sendediode links
PB7 SCK Out 0 ISP SL4 ISP: 7 Status LED 4 ***
PC0 SCL In/Out ² 0 I2C-Bus SCL XBUS: 10 XBUS SCL
PC1 SDA In/Out 0 I2C-Bus SDA XBUS: 12 XBUS SDA
PC2 TCK Out 0 JTAG DIR_L * Fahrtrichtung linke Kette
PC3 TMS Out 0 JTAG DIR_R * Fahrtrichtung rechte Kette
PC4 TDO Out 0 JTAG SL1 Status LED 1 ***
PC5 TDI Out 0 JTAG SL2 Status LED 2 ***
PC6 TOSC1 Out 0 SL3 Status LED 3
PC7 TOSC2 Out 0 ACS_R ACS Sendediode rechts
PD0 RXD In 1 RS232 RX PRG/U: 2 Soll: Pullup ?
PD1 TXD Out 0 RS232 TX PRG/U: 3
PD2 INT0 In 0 ENC_L Radencoder links
PD3 INT1 In 0 ENC_R Radencoder rechts
PD4 OC1B Out 0 MOTOR_L * PWM Motor links
PD5 OC1A Out 0 MOTOR_R * PWM Motor rechts
PD6 ICP Out 0 ACS_PWR ACS Sendedioden Power
PD7 OC2 Out 0 IRCOMM IRCOMM Sendedioden

Zeichen:

*   Standard-Belegung! Über Jumper/Lötbrücke änderbar.
**  Power On für Radencoder, IR-Empfänger, Stromsensoren, PWRON-LED
*** Zusätzlicher Anschluß von Bumpern oder Tastern möglich!
²   I2C-Master: Out, Slave: In
Timer-Nutzung

RP6Control Library

Konfiguration

Die wesentlichen Hardware-Konfigurationen des RP6 sind in der "RP6Config.h" zu finden. Sie enthält Festlegungen zu:

  • Encoder-Auflösung
  • Rotations-Faktor
  • Geschwindigkeits-Messintervall
  • Power On Warnung
  • ACS

Diese Header-Datei wird standardmäßig auch in die RP6Control Library eingebunden.

Port-Verwendung

Die Verwendung der Ports des RP6 CONTROL M32 Microcontrollers wird festgelegt in der Header-Datei "RP6Control.h". In dieser Datei wird auch noch Folgendes aufgeführt:

  • Quarzfrequenz (F_CPU)
  • True/false Definition
  • Baudrate der seriellen Schnittstelle

Hier eine Tabelle mit den Port-Definitionen der M32:

Port Name In/Out Pullup Wert Funktion Bezeichnung Stecker Anmerkungen
PA0 ADC0 In 0 MIC * ADC_MIC
PA1 ADC1 In 0 KEYPAD * ADC_KEYPAD
PA2 ADC2 In 0 ADC2 ADC: 2 ADC_2 (frei)
PA3 ADC3 In 0 ADC3 ADC: 1 ADC_3 (frei)
PA4 ADC4 In/(Out) 0 ADC4 ADC: 3 ADC_4 (frei)
PA5 ADC5 In 0 ADC5 ADC: 5 ADC_5 (frei)
PA6 ADC6 In 0 ADC6 ADC: 7 ADC_6 (frei)
PA7 ADC7 In 0 ADC7 ADC: 9 ADC_7 (frei)
PB0 T0/XCK Out 1 MEM_CS EEPROM Chip Select
PB1 T1 Out 1 MEM_CS2 EEPROM Chip Select 2 **
PB2 AIN0/INT2 In 1 EINT3 (INT2) * XBUS: 9 XBUS INT3
PB3 AIN1/OC0 Out 0 LCD_RS LCD: 4 LCD RS
PB4 SS Out 0 LCD_EN LCD: 6 LCD EN
PB5 MOSI Out 0 ISP MOSI ISP: 1 EEPROM SI
PB6 MISO In 0 ISP MISO ISP: 9 EEPROM SO
PB7 SCK Out 0 ISP SCK ISP: 7 EEPROM SCK
PC0 SCL In/Out ² 0 I2C-Bus SCL XBUS: 10 XBUS SCL
PC1 SDA In/Out 0 I2C-Bus SDA XBUS: 12 XBUS SDA
PC2 TCK In/Out 1 JTAG IO_PC2 I/O: 7 frei
PC3 TMS In/Out 1 JTAG IO_PC3 I/O: 5 frei
PC4 TDO In/Out 1 JTAG IO_PC4 I/O: 6 frei
PC5 TDI In/Out 1 JTAG IO_PC5 I/O: 3 frei
PC6 TOSC1 In/Out 1 IO_PC6 I/O: 4 frei
PC7 TOSC2 In/Out 1 IO_PC7 I/O: 1 frei
PD0 RXD In 0 RS232 RX PRG/U: 2
PD1 TXD Out 0 RS232 TX PRG/U: 3
PD2 INT0 In 0 EINT1 (INT0) XBUS: 8 XBUS INT1, Soll: Pullup 1
PD3 INT1 In 0 EINT2 (INT1) * XBUS: 11 XBUS INT2, Soll: Pullup 1
PD4 OC1B Out 0 STR IC3 STR
PD5 Oc1A In/Out 1 IO_PD5 I/O: 9 frei
PD6 ICP In/Out 1 IO_PD6 I/O: 8 frei
PD7 OC2 Out 0 BUZ Beeper

Zeichen:

*   Standard-Belegung! Über Jumper/Lötbrücke änderbar.
**  Frei nutzbar. wenn kein 2. EEPROM (IC5) eingesetzt ist!
²   I2C-Master: Out, Slave: In
Timer-Nutzung

RP6uart Library

RP6I2Cmaster/slaveTWI Library

WinAVR

Programmer's Notepad 2

GCC

Projekte

RP6 Base

CONTROL M32

RP6 CCPRO M128

Die CCPRO M128 wird in BASIC oder CompactC programmiert.

Demo-Programme

Die RP6 CCPRO M128 Demo Programme sind hier zu finden.

CompactC

Hier ein Beispiel in CompactC:


// WICHTIG: Immer die RP6CCLib mit einbinden:
#include "../../RP6CCLib/RP6CCLib.cc"

void main(void)
{
    // WICHTIG! Immer als erstes aufrufen:
	RP6_CCPRO_Init(); // Auf Startsignal warten, LCD und andere Dinge initialisieren !

    // ------------------------------------------------------

    // Zwei Zeilen Text mit dem LCD anzeigen:
    showScreenLCD("RP6 CCPRO M128", "Hello World!");

    // Zweimal piepsen:
    beep(200,300,100);   // Format: beep (<tonhöhe>, <dauer>, <pause>)
    beep(100,100,100);

    // 2 Sekunden Pause:
    AbsDelay(2000);

    // Untere Zeile im LCD löschen:
    clearPosLCD(1,0,16);


    // ------------------------------------------------------
    // Lauflicht:
    byte runLight, dir;   // Variablen deklarieren
    runLight = 1;  // Lauflicht Variable
    dir = 0;       // Laufrichtung des Lauflichtes
    while(true)
    {
		// LEDs setzen:
		setLEDs(runLight);

        // Laufrichtung wechseln wenn die äusseren LEDs erreicht wurden:
		if(runLight >= 16) {
            dir = 1;
            // Laufrichtung im LCD anzeigen:
            setCursorPosLCD(1,4);
            printLCD("<<<<----");
        }
		else if (runLight <= 1) {
			dir = 0;
            // Laufrichtung im LCD anzeigen:
            setCursorPosLCD(1,4);
            printLCD("---->>>>");
        }

		// LED Bit weiter "shiften" - nach links oder rechts, je nach Richtung:
		if(dir == 0)
			runLight = runLight << 1;
		else
			runLight = runLight >> 1;

        // 150ms Pause:
        AbsDelay(150);
    }
}

BASIC

Hier das selbe Beispiel in BASIC:


' WICHTIG: Immer die RP6CCLib mit einbinden:
#include "../../RP6CCLib/RP6CCLib.cbas"

Sub main()
    Dim runLight, dir As Byte  ' Variablen deklarieren

    ' WICHTIG! Immer als erstes aufrufen:
	RP6_CCPRO_Init()  ' Auf Startsignal warten, LCD und andere Dinge initialisieren!

    ' ------------------------------------------------------

    ' Zwei Zeilen Text mit dem LCD anzeigen:
    showScreenLCD("RP6 CCPRO M128", "Hello World!")

    ' Zweimal piepsen:
    beep(200,300,100)   ' Format: beep (<tonhöhe>, <dauer>, <pause>)
    beep(100,100,100)

    ' 2 Sekunde Pause:
    AbsDelay(2000)

    ' Untere Zeile im LCD löschen:
    clearPosLCD(1,0,16)


    ' ------------------------------------------------------
    ' Lauflicht:

    runLight = 1  ' Lauflicht Variable
    dir = 0       ' Laufrichtung des Lauflichtes
    Do While True
        setLEDs(runLight)  ' LEDs setzen

        ' Laufrichtung wechseln wenn die äusseren LEDs erreicht wurden:
		If runLight >= 16 Then
            dir = 1
            ' Laufrichtung im LCD anzeigen:
            setCursorPosLCD(1,4)
            printLCD("<<<<----")
		Else
            If runLight <= 1 Then
              dir = 0
              ' Laufrichtung im LCD anzeigen:
              setCursorPosLCD(1,4)
              printLCD("---->>>>")
            End If
        End If

		' LED Bit weiter "shiften" - nach links oder rechts, je nach Richtung:
		If dir = 0 Then
			runLight = runLight << 1
		Else
			runLight = runLight >> 1
        End If

        AbsDelay(150)  ' 150ms Pause
    End While
End Sub

Library

Versionen

Hier eine Tabelle der (mir) bekannten Library Versionen der CCPRO M128:

ZIP-Datum Version CompactC Version BASIC Bemerkungen
22.10.2008 1.0_16.10.08 1.0_07.10.08
31.01.2009 1.0_16.10.08 1.0_07.10.08 wie 22.10.2008 (?)

Konfiguration

Die wesentlichen Hardware-Konfigurationen des RP6 finden sich am Anfang der M128 Library (RP6CClib). Dort stehen Festlegungen zu:

  • Encoder-Auflösung

Port-Verwendung

Am Anfang der M128 Library (RP6CClib) werden Bezeichnungen für einige Portpins der RP6 CCPRO M128 definiert, siehe Abschnitt "C-Control Standard Port Konfiguration auf dem RP6 CCPRO M128 Modul". Diese Bezeichnungen kann man anstelle der Port-Bit Nummern verwenden, um das Programm selbsterklärender zu machen.

Die Festlegungen zur Verwendung der Ports des RP6 CCPRO M128 Microcontrollers finden sich am Ende der M128 Library in der Funktion RP6_CCPRO_Init(). Dort wird auch noch Folgendes gemacht:

  • SPI Initialisierung
  • Schieberegister zurücksetzen
  • Serielle Schnittstelle initialisieren
  • I2C Modul initialisieren
  • LCD initialisieren

Hier eine Tabelle mit den Port-Definitionen der M128:

Port Name In/Out Pullup Wert Port-Bit Bezeichnung Stecker Anmerkungen
PA0 AD0 0 AD0 * PORTA: 2 IC2/3 AD0
PA1 AD1 1 AD1 * PORTA: 1 IC2/3 AD1
PA2 AD2 2 AD2 * PORTA: 3 IC2/3 AD2
PA3 AD3 3 AD3 * PORTA: 4 IC2/3 AD3
PA4 AD4 4 AD4 * PORTA: 5 IC2/3 AD4
PA5 AD5 5 AD5 * PORTA: 7 IC2/3 AD5
PA6 AD6 6 AD6 * PORTA: 6 IC2/3 AD6
PA7 AD7 7 AD7 * PORTA: 8 IC2/3 AD7
PB0 SS In ** 8 PORT_SS SPI_I/O: 5 frei
PB1 SCK Out 1 9 PORT_SCK SPI_I/O: 6 IC4 CLK
PB2 MOSI Out 1 10 PORT_MOSI SPI_I/O: 3 IC4 D
PB3 MISO In 11 PORT_MISO SPI_I/O: 4 frei
PB4 OC0 12 PB4 SPI_I/O: 2 frei
PB5 OC1A 13 PB5 I/O: 1 SERVO: 1, frei
PB6 OC1B 14 PB6 I/O: 3 SERVO: 2, frei
PB7 OC2/OC1C 15 PB7 SPI_I/O: 1 SERVO: 3, frei
PC0 A8 16 A8 * PORTC: 1 IC3 A8
PC1 A9 17 A9 * PORTC: 2 IC3 A9
PC2 A10 18 A10 * PORTC: 3 IC3 A10
PC3 A11 19 A11 * PORTC: 4 IC3 A11
PC4 A12 20 A12 * PORTC: 5 IC3 A12
PC5 A13 21 A13 * PORTC: 6 IC3 A13
PC6 A14 22 A14 * PORTC: 7 IC3 A14
PC7 A15 23 A15 * PORTC: 8 IC3 A15
PD0 SCL/INT0 In 24 PORT_SCL XBUS: 10 XBUS SCL
PD1 SDA/INT1 In 0 25 PORT_SDA XBUS: 12 XBUS SDA
PD2 RXD1/INT2 26 PORT_RXD1 I/O: 8 RX 1, frei
PD3 TXD1/INT3 27 PORT_TXD1 I/O: 7 TX 1, frei
PD4 ICP1 28 PD4 (A16) * I/O: 6 frei (IC3 A16)
PD5 XCK1 Out 0 29 PORT_LCD_EN LCD: 6 LCD EN
PD6 T1 30 PD6 I/O: 4 frei
PD7 T2 31 PD7 I/O: 2 frei
PE0 RXD0/PDI 32 PORT_RXD0 PRG/U: 2 RX 0
PE1 TXD0/PDO 33 PORT_TXD0 PRG/U: 3 TX 0
PE2 XCK0/AIN0 Out 0 34 PORT_STR IC4 STR
PE3 OC3A/AIN1 Out 0 35 PORT_SND * I/O: 5 Beeper
PE4 OC3B/INT4 In ** 36 PORT_START PRG/U: 4 ² START_BOOT Taster
PE5 OC3C/INT5 In 37 PORT_PE5_INT * SPI_I/O: 7, XBUS: 8 XBUS INT1
PE6 T3/INT6 In 38 PORT_PE6_INT * XBUS: 9 XBUS INT3
PE7 ICP3/INT7 39 PE7 (EXT-DATA) SPI_I/O: 8 frei
PF0 ADC0 40 ADC0 ADC: 4 frei
PF1 ADC1 41 ADC1 ADC: 1 frei
PF2 ADC2 42 ADC2 ADC: 6 frei
PF3 ADC3 43 ADC3 ADC: 3 frei
PF4 ADC4/TCK 44 ADC4 ADC: 8 YADC4, frei
PF5 ADC5/TMS 45 ADC5 ADC: 5 frei
PF6 ADC6/TDO 46 ADC6 ADC: 7 YADC6, frei
PF7 ADC7/TDI 47 ADC7 ADC: 2 frei
PG0 WR 48 WE IC3 WE/
PG1 RD 49 OE IC3 OE/
PG2 ALE 50 ALE IC2 C
PG3 TOSC2 Out 0 51 PORT_LED1 * JP: YL1 Status LED1
PG4 TOSC1 Out 0 52 PORT_LED2 * JP: YL3 Status LED2

Zeichen:

*    Standard-Belegung! Über Jumper/Lötbrücke änderbar.
**   Pullup-Widerstand 100 kOhm!
²    Über Serienwiderstand 470 Ohm

Timer-Nutzung

CompactC

BASIC

C-Control Pro

IDE

CompactC

BASIC

Projekte

CompactC

BASIC

Erfahrungsberichte

...in Arbeit...(kann aber gerne ergänzt werden)



Siehe auch


Weblinks


Autoren

--Sloti 22:23, 29. Dez 2007 (CET)

--Tobias1 18:30, 06. April 2010 (CET)

--Dirk 20:50, 03. August 2011 (CET)


LiFePO4 Speicher Test