You are on page 1of 20

C++ I/O hardware programming

PC speaker / PIT control

author: Dominiek ter Heide <dodo@darkwired.org>


date: 03-0ct-2003
version: 1.0
http://www.darkwired.org/
Contents.
1 Introduction
2 Education objectives
3 License
4 General concept
5 Intel timers
6 Controlling the Intel PIT chip
7 Controlling the PC-speaker
8 Programming: Including the correct headers.
9 Programming: getting the correct permissions
10 Programming: I/O'ing the right stuff
11 References / Sources of information
12 Appendix: a code snippet
1. Introduction.
I've been programming for some time now and had a lot of
fun researching and controlling the PC speaker. I am now
sharing this knowledge.
Before we will begin deploying our C++ skills, we will go
through some general information.
My english is kinda bad, so forgive me for that.
2. Education objectives.
– general C++ I/O hardware programming
– understanding the Intel PIT chip
– understanding the IBM PC speaker
– basic low level programming
– C++ hardware I/O functions in: Linux, FreeBSD, Win32/DOS
3. License.
copyright © 2003 Dominiek ter Heide
Permission is granted to copy, distribute and/or modify
this document under the terms of the GNU Free Documentation
License, Version 1.2 or any later version published by the
Free Software Foundation; with no Invariant Sections, no
Front-Cover Texts, and no Back-Cover Texts. A copy of the
can be found at:
http://www.gnu.org/licenses/fdl.txt
4. General concept.
To make some noise with the PC speaker and to understand
it, we have to do these general steps:
– understand the Intel Timers
– set the Intel timer 2
– hook timer 2 to the PC speaker
5. Intel timers.
To control the PC speaker we must use the Intel Timer 2. We
can send a command to the PC speaker to use this timer.
Therefore we must know something about these timers.
Timer 2 is also known as PIT channel 2, PIT means
Programmable Interval Timer. The Intel PIT chip (8253/8254)
has 3 channels, channel 0, 1 and 2.
Channel 0 is used for updating the system clock, channel 0
usually ticks at a rate of 18.2 ticks a second (18.2 Hz).
Channel 1 is used for refreshing the DMA (Direct Memory
Access) controller.
We will be using channel 2, because this channel is
preserved for generating tones.
The PIT chip knows the following hardware I/O ports:

0x40 channel 0 read/write


0x41 channel 1 read/write
0x42 channel 2 read/write
0x43 control write

So we will just be using port 0x42 and 0x43. We use 0x43 to


set the timer frequency.
6. Controlling the Intel PIT chip.
We want to change the frequency of the PIT channel 2.
Therefore we must change the channel 2 mode and countdown
value.
To change the PIT chip's settings for a channel, we have to
send a control byte to the control port. The format of this
byte is shown below:

Bit: 7 6 5 4 3 2 1 0
value: 128 64 32 16 8 4 2 1
------- ------- ----------- ---
channel order mode format

I'm not going in too deeply on the modes, we will use mode:
0 1 1, so this means we have to set bit 1 and 2.
As for the channels:
channel 0 bits 0 0
channel 1 bits 0 1
channel 2 bits 1 0
so naturally we need to set bits 6 and 7 to: 1 0, we don't
wanna mess up our DMA refreshing.
The format bit specifies wether it's in BCD (Binary Code
Decimal) or binary. We'll use the last one.
After sending this control byte, we want to change the
value of of channel 2, so we have to put in the new
countdown value. This countdown value is calculated in the
following way:

countdown = 1193180 / frequency;

where fequency is the desired frequency, 1193180 is the


system's oscillator's frequency.
This countdown value can be 1 – 65535, so we need to send
more than just one byte (255) to to the PIT chip.
As for the order, we will use 1 1, this way we will send
the lower byte to the PIT first, the higher byte comes
second.
So now we have our controlbyte:

Bit: 7 6 5 4 3 2 1 0
value: 128 64 32 16 8 4 2 1
cond.: 1 0 1 1 0 1 1 0
------- ------- ----------- ---
channel order mode format

This brings our controlbyte to a value of: 182 (0x6b)


7. Controlling the PC Speaker.
The PC Speaker channel 2 is connected to the PIT chip with
an AND gate. To make the AND operation true, u have to send
a 1 to bit 1 of the PC speaker port. The hardware address
for this port is: 0x61

Let the coding begin.


8. Programming: Including the correct headers.
Ok, I'm gonna show how you can code the above things in
C++. I will show how to port it to: FreeBSD, Linux and
Win32.
To begin our code, we need some libs, so we are going to
include some header files:

#include <stdio.h>
#include <stdlib.h>

FreeBSD:
#include <sys/types.h>
#include <machine/cpufunc.h>
#include <machine/sysarch.h>
#include <unistd.h>

Linux:
#include <sys/io.h>
#include <unistd.h>

Win32/DOS:
#include <conio.h>
9. Programming: getting the correct permissions.
ok, now, to control hardware devices we must have
permission to use these devices, right? On FreeBSD and
Linux we can use the ioperm permission functions, but Win32
doesn't have these. It's offcourse logical that all users
can mess up devices right? (sarcasm)

FreeBSD:
i386_get_ioperm(int start, int length, int enable);

Linux:
ioperm(int start, int length, int enable);

The start integer indicates the port we want to get


permission to. So we will need permissions for: 0x42 (PIT
channel 2), 0x43 (PIT control), 0x61 (PC speaker).
The length integer indicates how many bits we need to get
permission to, we will need 32 from port 0x61 and 2 from
0x42. We now also have permission to access port 0x43.
We now have the following code:

FreeBSD:
i386_set_ioperm(0x61, 0x20, 1);
i386_set_ioperm(0x42, 0x02, 1);

Linux:
ioperm(0x61, 0x20, 1);
ioperm(0x42, 0x02, 1);

So now we have permission to the ports and can do whatever


things we want.
10. Programming: I/O'ing the right stuff
The first thing to do now is setting the PIT channel 2. We
need to send the correct controlbyte to port 0x43 for this.
This will be: 182 (0x6b).
After this we need to send the new countdown channel to
channel 2, then we have to enable the PC Speakers bit 1.
We need some input / output functions now:

FreeBSD:
int inb(port);
outb(int port, int value);

Linux:
int inb(port);
outb(int value, int port);

Win32/DOS:
int _inp(port);
_outp((unsigned short)int port, int value);

We could also use assembly instructions for this:


in port, value
out port, value

After sending the controlbyte, we need to send the new


countdown value to channel 2.
countdown = 1193180 / frequency;
We can only send one byte, so we need to split the coundown
int into 2 bytes.
First we send the lower byte.
We can calculate the lower and higher byte using the
following methods:

lo = countdown & 0xff;


hi = (countdown >> 8) & 0xff;

The first operation returns us the value of countdown bits


1 – 255(0xff). The second operation shifts all bits down 8
places, so we will get the higher byte in place of the
lower byte. It then uses the same trick to get the lower
byte: & 0xff;
A second way to do this:

__asm__ (
"mov %%ax, %%bx;"//move our byte in 16bits: ax
"movb %%bl, %%cl;"//move lower byte reg to cl
"movb %%bh, %%dl;"//move higher byte reg to dl
:"=c"(lo), "=d"(hi) //our output
:"a"(byte) //our input
);

This is done by using inline assembly instructions (GNU


syntax).
We now have the following I/O code:

int frequency = 4000;


int countdown = 1193180 / frequency;
int lowerbyte = countdown & 0xff;
int higherbyte = (countdown >> 8) & 0xff;
//now some platform dependend code:

FreeBSD:
outb(0x43, 0x6b);
outb(0x42, lowerbyte);
outb(0x42, higherbyte);
int value = inb(0x61);
value = value | 3;
outb(0x61, value);
sleep(1);
value = inb(0x61);
value = value & 0xfc;
outb(0x61, value);

Linux:
outb(0x6b, 0x43);
outb(lowerbyte, 0x42);
outb(higherbyte, 0x42);
int value = inb(0x61);
value = value | 3;
outb(value, 0x61);
sleep(1);
value = inb(0x61);
value = value & 0xfc;
outb(value, 0x61);
Win32/DOS:
_outp(0x43, 0x6b);
_outp(0x42, lowerbyte);
_outp(0x42, higherbyte);
int value = _inp(0x61);
value = value | 3;
_outp(0x61, value);
sleep(1);
value = _inp(0x61);
value = value & 0xfc;
_outp(0x61, value)
ok, let's review:
we use int value = inb(0x61); to get the current value of
this port. After this we make sure bit 0 and 1 (values: 1 +
2 = 3) are set, by doing: value = value | 3; outb(0x61,
value);
by doing this, the speaker makes noise. Then after sleep
(1); the speaker stops by doing:

value = inb(0x61);
value = value & 0xfc;
outb(0x61, value);
11. References / Sources of information.

The Intel 8253/8254 PIT chip:


http://faculty.plattsburgh.edu/stephen.linder/Academic/Orga
nization/docs/PC%20Timer%208253.html
http://www.cs.binghamton.edu/~reckert/220/8254_timer.html

The Intel PC speaker:


http://www.bsdg.org/swag/SOUND/0107.PAS.html
http://fly.cc.fer.hr/GDM/articles/sndmus/speaker1.html
http://fly.cc.fer.hr/GDM/articles/sndmus/speaker2.html

Hardware I/O methods:


FreeBSD/Linux manual pages
DOS console I/O header: conio.h
12. Appendix: a code snippet.
You can also get this at:
http://www.darkwired.org/pub/code/snippets_c/hardware/darks
peaker/

Note: to compile with MSVC++ or GCC

1 /*
2
3 DarkSpeaker 0.6
4 pc speaker control program
5 Linux, FreeBSD, Win9x
6
7 author: dodo <dodo@darkwired.org>
8 contact: http://www.darkwired.org/
9
10 this code is licensed under the GNU GPL
11 by compiling this, you agree to this license
12
13 notes:
14 - now compat with MSVC++ / win9x (juckie:p)
15 - for porting to WinNT, change _outp/_inp
16
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21
22 #ifdef __FreeBSD__
23 #include <sys/types.h>
24 #include <machine/cpufunc.h>
25 #include <machine/sysarch.h>
26 #include <unistd.h>
27 #elif defined WIN32
28 #include <conio.h>
29 #else
30 #include <sys/io.h>
31 #include <unistd.h>
32 #endif
33
34
35 class DarkSpeaker {
36 public:
37 //main functions:
38 bool TimerSetFrequency(int herz);
39 bool TimerSpeakerConnect(void);
40 bool TimerSpeakerDisconnect(void);
41 bool SpeakerBeep(int herz, int microseconds);
42 //io functions:
43 bool IOperm(int port, int num);
44 bool IOoutb(int port, int value);
45 long IOinb(int port);
46 //misc funtions:
47 void GetHLbytes(int byte, int *low, int *high);
48 };
49
50 int port_timer2a = 0x43;
51 int port_timer2b = 0x42;
52 int port_speaker = 0x61;
53 DarkSpeaker DarkSpeaker;
54
55 int main(int argc, char *argv[])
56 {
57
58 if(argc<3) {
59 printf(
60 "usage: %s <frequency/hz> <duration/ms>\n"
61 "DarkSpeaker by dodo <dodo@darkwired.org>\n"
62 ,argv[0]
63 );
64 return 0;
65 }
66
67 if(DarkSpeaker.IOperm(port_speaker, 0x20)==false) {
68 printf("could not get 0x61 I/O permissions\n");
69 return 0;
70 }
71
72 if(DarkSpeaker.IOperm(port_timer2b, 0x02)==false) {
73 printf("could not get 0x42 I/O permissions\n");
74 return 0;
75 }
76
77
78 DarkSpeaker.SpeakerBeep(atoi(argv[1]), atoi(argv[2]));
79 return 1; //MSVC++
80 }
81
82
83 bool DarkSpeaker::TimerSetFrequency(int herz)
84 {
85 int countdown;
86 int lc, hc;
87 /* we need to know the countdown value for timer2 */
88 countdown = 1193180 / herz;
89
90 GetHLbytes(countdown, &lc, &hc);
91 IOoutb(port_timer2a, 0xb6); // tell timer 2 we want to
change countdown
92 IOoutb(port_timer2b, lc); //put in lower byte of
countdown value
93 IOoutb(port_timer2b, hc); //put in higher byte of
countdown value
94
95 return false;
96 }
97
98
99 bool DarkSpeaker::TimerSpeakerConnect(void)
100 {
101 int value;
102 value = IOinb(port_speaker);
103 //printf("value: %d\n", value);
104 value = value | 3;
105 IOoutb(port_speaker, value);
106 return false;
107 }
108
109 bool DarkSpeaker::TimerSpeakerDisconnect(void)
110 {
111 int value;
112 value = IOinb(port_speaker);
113 //printf("value: %d\n", value);
114 value = value & 0xfc;
115 IOoutb(port_speaker, value);
116 return false;
117 }
118
119 bool DarkSpeaker::IOperm(int port, int num)
120 {
121 #ifdef WIN32
122 return true;
123 #elif defined __FreeBSD__
124 if(i386_set_ioperm(port, num, 1))
125 #else
126 if(ioperm(port, num, 1))
127 #endif
128 return false;
129 return true;
130 }
131
132 bool DarkSpeaker::IOoutb(int port, int value)
133 {
134 #ifdef __FreeBSD__
135
136 outb(port, value);
137
138 #elif defined WIN32
139
140 /* old win32 stuff
141 unsigned short int prt = (unsigned short int)port;
142 char val = (char)value;
143
144 __asm
145 {
146 mov dx, prt
147 mov al, val
148 out dx, al
149 }
150 */
151 _outp((unsigned short)port, value);
152
153 #else
154
155 outb(value, port);
156
157 #endif
158
159 return false;
160 }
161
162
163 long DarkSpeaker::IOinb(int port)
164 {
165 #ifdef WIN32
166 int val;
167 /* old win32 stuff
168 unsigned short int prt = (unsigned short int)port;
169 unsigned char val;
170
171 __asm
172 {
173 mov dx, prt
174 in al, dx
175 mov val, al
176 }
178 val = _inp(port);
179
180 return val;
181
182 #else
183
184 return inb(port);
185
186 #endif
187 }
188
189 void DarkSpeaker::GetHLbytes(int byte, int *low, int *high)
190 {
191 char lo;
192 char hi;
193
194 #ifdef __Linux__
195 __asm__ (
196 "mov %%ax, %%bx;" //move the byte in 16bits: ax
197 "movb %%bl, %%cl;" //move lower byte reg to cl
198 "movb %%bh, %%dl;" //move higher byte reg to dl
199
200 :"=c"(lo), "=d"(hi) //the output
201 :"a"(byte) //the input
202 );
203 #else
204 lo = byte & 0xff;
205 hi = (byte >> 8) & 0xff;
206 #endif
207
208 //printf("high = %d\nlow = %d\nbyte = %d\n\n", hi, lo, byte);
209
210 *low = (int)lo;
211 *high = (int)hi;
212 }
213
214 bool DarkSpeaker::SpeakerBeep(int herz, int microseconds)
215 {
216 TimerSetFrequency(herz);
217 TimerSpeakerConnect();
218 #ifdef WIN32
219 _sleep((int)(microseconds/1000000)); // need something for this
220 #else
221 usleep(microseconds);
222 #endif
223
224 TimerSpeakerDisconnect();
225 return false;
226 }
227