/* Square wave output program swave12a, based on the square wave output program from https://rt.wiki.kernel.org/index.php/Squarewave-example but with different frequencies at D3 ... D7 and with output also at C0 ... C3, to see the square waves with the eyes. That program makes output on the 8 data pins of the parallel port, as the old program, plus output on the 4 controll pins. Output at 50 mikrosecends timer period: 10 kHz on pin 2 = D0 5 kHz on D1 2.5 kHz on D2 1 kHz on D3 500 Hz on D4 250 Hz on D5 100 Hz on D6 50 Hz on D7 20 Hz on C0 8 Hz on C1 2.5 Hz on C2 1 Hz on C3 The frequency step from one pin to the next is with a factor of 2 to 3.2. So your eyes can see the toggling at the controll port pins, at about 9 % CPU load on a 3 GHz core. Compile using "gcc -Ofast -o swave12a swave12a.c -lrt -Wall" First argument: The realtime priority. Second argument: Interval length / ns. Third argument: Base address of the parallel port, e. g. found from findpar.sh. Example: ./swave12a 98 50000 0xc0b0 This program should run under a RT Linux Kernel, so with the with a RT_PREEMPT patch and RT configuration, but it also works most of the time very well under lowlatency kernels. Usually it should be used with the second highest RT priority, value 98. See https://rt.wiki.kernel.org/index.php/HOWTO:_Build_an_RT-application#Priority_99 and the start script startsw12a.sh. Dr. Rolf Freitag (rolf dot freitag at email dot de), 2017-01-16 */ #include #include #include #include #include #include #include #define PORT 0x378 // Default port (base): First (onboard) parallel port #define NSEC_PER_SEC 1000000000 // 1e9 unsigned int ui_base_port=0; // base port of the parallel port // using clock_nanosleep of librt extern int clock_nanosleep (clockid_t __clock_id, int __flags, __const struct timespec *__req, struct timespec *__rem); /* the struct timespec consists of nanoseconds * and seconds. if the nanoseconds are getting * bigger than 1000000000 (= 1 second) the * variable containing seconds has to be * incremented and the nanoseconds decremented * by 1000000000. */ static inline void tsnorm (struct timespec *ts) { while (ts->tv_nsec >= NSEC_PER_SEC) { ts->tv_nsec -= NSEC_PER_SEC; ts->tv_sec++; } } /* Increment the loop counter and write to the parallelport, with a simple cycle which is low at the first half. Cycle length (in unit function calls, u64_state increments): 2 at D0 4 at D1 8 at D2 20 at D3 40 at D4 80 at D5 200 at D6 400 at D7 1000 at C0 2500 at C1 8000 at C2 20,000 at C3 */ void out () { static unsigned int u64_state; // static is initialized with zero in standard/ANSI C unsigned char byte0=0; // Byte for writing to the ports u64_state++; // increase the loop counter // start with setting the value for BIT0 and BIT1 and higher bits set to zero byte0 = (unsigned char) (u64_state bitand 0x7); // At D0/Pin 2 10 kHz, at D1/Pin 3 5 kHz, at D2 2.5 kHz // D3: 20 / cycle, 1 kHz if ( (u64_state/10) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 8; // set BIT3 // D4: 40 / cycle, 500 Hz if ( (u64_state/20) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 0x10; // set BIT4 // D5: 80 / cycle, 250 Hz if ( (u64_state/40) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 0x20; // set BIT5 // D6: 200 / cycle, 100 Hz if ( (u64_state/100) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 0x40; // set BIT6 // D7: 400 / cycle, 50 Hz if ( (u64_state/200) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 0x80; // set BIT7 // output to the data port, write byte0 outb (byte0, ui_base_port); // Now the 4 bits for the status port, without inversion for inverted pins because the // phase does not matter. // C0: 1000 / cycle, 20 Hz byte0 = (unsigned char) ( (u64_state/500) bitand 1 ); // at the second half of the cycle set to high // C1: 2500 / cycle, 8 Hz if ( (u64_state/1250) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 2; // set BIT1 // C2: 8000 / cycle, rounded to 13.334 / cycle, 2.5 Hz if ( (u64_state/4000) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 4; // set BIT2 // C3: 20,000 / cycle1, 1 Hz if ( (u64_state/10000) bitand 1 ) // at the second half of the cycle set to high byte0 or_eq 8; // set BIT3 // output to the controll port, write byte0 outb (byte0, ui_base_port +2); return; } // out() int main (int argc, char **argv) { struct timespec t; struct sched_param param; // default interval = 50000ns = 50us, cycle duration = 100us int interval = 50000; // Second argument: Interval length / ns if (argc >= 3) interval = atoi (argv[2]); // Third argument: Base address of the parallel port, e. g. found from findpar.sh. // THIS VALUE SHOULD BE COORECT BECAUSE THIS PROGRAM WRITES WITHOUT CHECKS TO // THIS ADDRESS AND TWO BYTES HIGHER! if (argc >= 4) ui_base_port = (unsigned int) strtol (argv[3], (char **) NULL, 0); // get the permission to read/write at the base ioperm (ui_base_port, 4, 1); // 4 bytes, 32 bit region, enable // First argument: The realtime priority. if (argc >= 2 && atoi (argv[1]) >= 0) { printf ("using realtime, priority: %d\n", atoi (argv[1])); param.sched_priority = atoi (argv[1]); // enable realtime fifo scheduling //if (sched_setscheduler (0, SCHED_FIFO, ¶m) == -1) if (sched_setscheduler (0, SCHED_RR, ¶m) == -1) { perror ("sched_setscheduler failed"); exit (-1); } } // set data port to zero outb(0, ui_base_port); // set controll port to zero, which sets the 4 output pins to logical zero and, // in bidirectional mode, sets the data port direction to output and disables the interrupt. outb(0, ui_base_port+2); // get current time clock_gettime (0, &t); // start after one second; warmup t.tv_sec++; while (1) { // wait untill next shot clock_nanosleep (0, TIMER_ABSTIME, &t, NULL); // do the stuff (output) out (); // calculate next shot t.tv_nsec += interval; tsnorm (&t); } return (0); } // main