The IR transmitter for the Air Swimmers shark operates at 38KHz. What this means is that, when the infrared LED is transmitting a command, it flickers on and off roughly 38,000 times per second. This allows the IR receiver to better distinguish the light coming from the LED from naturally-occurring light, and it also increases the lifespan of the IR LED, since this flickering effect reduces the overall power consumption.
While researching how to handle IR transmission on the Arduino, I came across an article by Ken Shirriff entitled Secrets of Arduino PWM. This article provides a pretty in-depth discussion on how the Arduino handles PWM. Not surprisingly, Shirriff also details an IR Remote implementation, which I’ve used as the basis for my implementation.
Configuring the IR Signal
The code below uses phase-correct PWM, and is taken with slight modification (primarily, inlining of a macro) from IRremote.cpp.
void IR::enableIROut(int khz) {
// Enables IR output. The khz value controls the modulation frequency in kilohertz.
// The IR output will be on pin 3 (OC2B).
// This routine is designed for 36-40KHz; if you use it for other values, it's up to you
// to make sure it gives reasonable results. (Watch out for overflow / underflow / rounding.)
// TIMER2 is used in phase-correct PWM mode, with OCR2A controlling the frequency and OCR2B
// controlling the duty cycle.
// There is no prescaling, so the output frequency is 16MHz / (2 * OCR2A)
// To turn the output on and off, we leave the PWM running, but connect and disconnect the output pin.
// Enable the PWM pin for output
pinMode(3, OUTPUT);
// Initialize the PWM pin to HIGH, defaulting the LED to off
// (the LED is turned on when the pin goes LOW).
digitalWrite(3, HIGH);
// Define the PWM frequency
const uint8_t pwmval = SYSCLOCK / 2000 / khz;
// WGM2 = 101: phase-correct PWM with OCRA as top
// CS2 = 001: no prescaling
TCCR2A = _BV(WGM20);
TCCR2B = _BV(WGM22) | _BV(CS20);
// The top value for the timer. The modulation frequency will be SYSCLOCK / 2 / OCR2A.
OCR2A = pwmval;
// Set a 33% duty cycle
OCR2B = pwmval / 3;
}
What this does is set up Timer 2 to count up from 0 to pwmval and then back down to 0. Whenever the timer value is between 0 and pwmval / 3, OC2B is set HIGH. For values between pwmval / 3 and pwmval, OC2B is set to LOW. With a modulation frequency of 38KHz, and a 16MHz CPU, pwmval is approximately equal to 210 (16000000 / 2000 / 38). Thus, pwmval / 3 = 70. With this configuration, after the initial iteration (which is only HIGH for half the time, and thus, not complete), OC2B is HIGH for 140 clock ticks and LOW for 280 clock ticks. With the 16MHz CPU, and no prescaling performed on the timer, a single clock tick takes ~62.5 nanoseconds. Thus, one PWM period lasts (140 + 280) * 62.5 nanoseconds, or approximately 26.25 microseconds. This results in a frequency of (1 / 0.00002625)Hz, or 38.095KHz, giving us our desired frequency.
Shirriff paints an easier-to-understand picture with the following image:

OCRnA represents pwmval and OCRnB represents pwmval / 3. OC2B is shown as OCnB at the bottom of the graph. For timer values below the orange OCRnB line, OCnB is HIGH. For values between the orange OCRnB line and the green OCRnA line, OCnB is LOW.
Transmitting an IR command
With the code outlined above, Timer 2 is configured to transmit the requisite 38KHz carrier wave required by the Air Swimmers IR receiver. However, we still need to incorporate logic for transmitting a series of 1′s and 0′s in the appropriate packet format for the Air Swimmers device. In Phase 1, I gathered the timing information for the IR packets transmitted by the Air Swimmers remote. Of note are the following:
- Gap Duration = 50000us
- Pulse Gap Duration = 340us
- Short Pulse Duration = 220us
- Long Pulse Duration = 720us
These define the four components of the IR packet shown below:

In this example, the Pulse Gap Duration refers to the length of time spent in the LOW setting between each bit transmitted, the Short Pulse Duration represents the length of the shorter pulse (represented as a 0 in the packet structure), and the Long Pulse Duration represents the length of the longer pulse (represented as a 1 in the packet structure). The Gap Duration is actually not shown, but refers to the minimum time to wait between sending two packets.
Given the above specifications, we can put together a method for transmitting an IR packet (here represented as a 32-bit integer):
void transmitIRPacket(uint32_t packet) {
uint8_t packetLength = 24;
uint16_t gapDuration = 50000ul;
uint16_t pulseGapDuration = 340ul;
uint16_t shortPulseDuration = 220ul;
uint16_t longPulseDuration = 720ul;
for(uint8_t i=packetLength;i>0;i--) {
TCCR2A &= ~_BV(COM2B1); // Enables PWM on Pin 3
if (bitRead(packet, i-1)) {
delayMicroseconds(longPulseDuration);
} else {
delayMicroseconds(shortPulseDuration);
}
TCCR2A |= BV(COM2B1); // Disables PWM on Pin 3
delayMicroseconds(pulseGapDuration);
}
delayMicroseconds(gapDuration);
}
Assuming the IR LED’s data pin is attached to Pin 3, this function should transmit an IR packet that the Air Swimmers IR receiver can interpret.