mailto: blog -at- heyrick -dot- eu

Figuring out an IR controller with an oscilloscope

I have two Silvercrest (Lidl) heaters. They both have IR controllers. The wall mounted one uses a standard NEC protocol so it was easy enough to brute force it.
The rotating fan heater, however, has resisted all attempts to brute force anything. It just won't respond.

So time to break out some diagnostic gear.

IR controller and oscilliscope
The controller and the oscilloscope.

Using the croc clips, it was simple enough to hook the oscilloscope across the IR LED to see what sort of signal actually being sent to the LED.

IR controllers work using two methods. The first method is a carrier frequency that is often in the range of 32-64kHz, and overlaid on that is the data. There are various encoding methods, but one of the most common is simply turning the LED on and off for different durations during the space of a 'bit'. Let's say, for instance, that the bit duration is divided into three parts. A '0' bit lights the LED for the first third and then turns if off for the remaining two. A '1' bit, by contrast, lights the LED for the first two parts, and then turns it off for the third. By doing it like this, it is possible to not only distinguish whether a '0' or '1' was received, but also knowing that the final third is always LED off, the entire message is self-clocking.

So, the first thing we need to do is work out the carrier frequency. For this, we shall pick a timebase of 50µs. By doing the maths: 1,000,000 microseconds in a second, divided by 50, the result is 20,000Hz per square. Therefore, with a 50µs time per square, each square will be 20kHz. From this, we can estimate what our clock speed is.

Note, by the way, that all of the images here are scaled 2× to be clearer in the text, the screenshots saved by the BSide OT3 are 320×240.

The carrier frequency.
The carrier frequency.

You can see that at first glance it's pretty much two pulses per square, or 25µs. But look closer, it's not lining up, there's some drift, but not a lot. Let's dive in deeper.
For some reason, the graticle is divided into six horizontally. Well, 50µs ÷ 6 means each sliver of a square is 8.33'µs.
Now let's see where one of the rising edges lines up. Second square from the left, lining up with the second dot in the square - the position marked 'A'.
Now let's count across to see how many squares it takes to drift to the third dot.

Understanding the carrier timing
Understanding the carrier timing.

As you can see, it takes four squares to drift from one dot to the next - the position marked 'B'. So we now divide the dot time (8.33'µs) by the number of squares spanned, this gives us the value of 2.08µs.
Let's just call it 2µs, okay? Now we add this into the 25 we rough-estimated earlier, and we finally arrive at a carrier that takes 27µs, which if we divide a million (microseconds in a second) by that 27µs, this tells us that our carrier frequency is 37,037kHz.
In reality, it is probably supposed to be around about 38kHz, but there is some leeway in the actual speeds. 37,037 is only 2.534% off of a perfect 38kHz, so it's well within spec; the timing is probably derived from some cheap resonator that began life in an AM radio divided down a bunch of times. Add to this the slop, then maybe it's a 300kHz resonator divided eight times?
Pure speculation, of course, but it's useful to speculate to understand why it's such a seemingly weird value.

 

Now let's look at the data that is actually sent.

A trace of a button press.
The signal of a button press.

And, for comparison, here is a different button.

A trace of another button press.
The signal of a different button press.

Looking at this, it would appear that the bit time is actually divided into four, with a zero being the first part LED on and the rest parts LED off, and a one being three parts LED on and the final part LED off.

Let's convert these pulses into bit values.

An annotation of what this bitstream actually is.
An annotation of what this bitstream actually is.

So from this we can tell that it's a really simple code. The entire message appears to be twelve bits that repeat; so it isn't the NEC style code that has a device address, then inverted, then a control code, then that inverted. The point of the inversion is to guarantee good reception of the signal - if the inverted, when uninverted, doesn't match the normal then the message can be ignored. This code here? No protection to ensure good reception.

I'm not sure if it is split evenly as handily shown by the scope's centre marker, into a six bit header and six bits of data; the repeated 110 at the start is interesting. But it could just as easily be 11 start and 011 device ID, followed by seven bits of data. I guess it depends if one thinks 32 or 64 buttons are enough.
I originally thought that maybe this was some version of the Sony SIRC encoding, but that has the data first and the device ID following, but here you can clearly see the change at the end, bits 9 and 11 changing for those button presses.

Also notable is that there doesn't appear to be any initial burst to wake up the IR receiver. I had to fiddle around a lot here as my new little oscilloscope appears to have a fair number of quirks regarding how it is triggered. The one-shot was useless, it was triggering on low level noise despite the trigger voltage being over a volt. So I had to use normal where it would trigger and hold until it saw something else.
Like a normal cathode ray oscilloscope, the sampling available is only that which is present on the screen. You can't scroll to the right or anything, but you can shove the horizontal trigger point over to the left to only capture what happens after the trigger; you can see this in my examples with the orange down-pointing triangle over on the left of the display.

At any rate, it looks like the command simply repeats frequently rather than having any preamble.

Now, to continue there are two ways forward. The first is to try to do some hellish calculations to work out how many microseconds the LED is on and off for. Given the accuracy of my oscilloscope and the state of my comprehension of mathematics, this is surely doomed to failure.

The app I'm using on my Xiaomi is called "irplus" and it can accept codes in a format called Pronto. This is exactly what we want, because it allows us to specify the carrier frequency, and then simply follow that with a raw report of how many carrier cycles the LED is on and off for.

But, wait, how many?

For this you'll need to pick a timebase that shows the carrier peaks separate enough that you can see them, but close enough that you can see all of them. Given we know that a bit takes a little under 2ms, and a zero pulse takes a quarter of that, so say half a millisecond, this suggests that 100µs would be a good value to pick as if we can see a zero pulse it will run for half a millisecond, or 500µs, or about half of the screen. You'll need to bang the buttons repeatedly until you actually get a zero bit on the screen, as you're much more likely to get nothing but carrier across the screen. But persevere, it'll arrive eventually, and when it does, it'll be perfect.

Counting the carrier cycles in a short burst.
Counting the carrier cycles in a short burst.

I'm not sure what's with that off-by-one-pixel for part of the display. I will say that I've noticed quite a few redraw bugs - particularly when using the device in WAIT or STOP mode, so it isn't constantly redrawing the screen. Still, considering the price I paid for the thing.....

Actually, it looks like the timing is closer to 400 rather than 500, but that's neither here nor there. What is important is that there are clearly sixteen carrier cycles for this zero bit. Which means the blank space following will be 16×3 or 48 cycles. And, in the inverse, a one bit will be 48 carrier cycles of the LED on, and 16 off.

Now let's turn this into something we can use. Pronto codes are always in hex. I am using Pronto with the irplus app because, as I said above, the timing is simply how many carrier cycles the LED is on and off for, which makes it pretty simple to sort this out by simply copying what is observed.

The first word is 0000. This is followed by the carrier frequency using the weird calculation of 1000000÷(nnn×0.241246) where you just plug in values for "nnn" until you get to something close to 38kHz. As it happens, 109 is 38028.86kHz which is "close enough". IR equipment typically has a tolerance of 5-10% because temperature, battery, really cheap and crappy timing hardware, etc. It's not going to fail if it's not bang on the exact right frequency. 109 in hex is 006D.
This is then followed by two words giving the size of the initial message, and the size of the subsequent messages. We can set the first to 0000 so it'll skip the initial one, and the second and subsequent as 000C (12 in denary) which are our twelve bits.
Finally, there are twenty four values, or twelve pairs of values. These values are the number of carrier cycles the LED is on for, and the number it is off for.

Translating from above, a zero bit would be 0010 0030 (as 16 is 10 in hex, and 48 is 30 in hex), whilst a one bit would be the inverse, 0030 0010.

Piling this all together, we can translate the bitstream 1 1 0 1 1 0 0 0 0 0 0 1 into the Pronto code sequence 0000 006D 0000 000C 0030 0010 0030 0010 0010 0030 0030 0010 0030 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0030 0010 0030 0030 0060.
The 0060 at the end is to force the LED off for a while, so when the sequence repeats it doesn't just crash into the end of the previous message.

I created a button in the irplus app, set it to the above code sequence, pointed it at my heater, and... it turned on!

That's because the second button that I tested was the on/off one (actually: off, low, high, auto, off, low (etc)). Arguably the useful one. I can work out the codes for the rest some other time.
The important point was "Can this be done like this?" and the result is, quite clearly, yes.

This is a diagram, created by the app, to visualise what is actually being transmitted.

The pattern generated by the program.
Does this pattern look familiar?

 

How did our examination and guessing do? Well, the chip inside is an eight pin HS95105SK and you can get a datasheet online...in Chinese. But from this we can see enough without resorting to Google Translate.

First up, the bit timing. Yup, it's about 400µs for a zero pulse, and it's 1-and-3 and 3-and-1, like I determined.

The bit timing from the datasheet.
The bit timing, from the datasheet.

And, finally, the data format. It's a fixed 110 at the start, then two bits for device ID, and finally seven bits of data. So the repeat 110110 was ultimately a red herring. This doesn't change anything, it simply clarifies what we had mostly determined (that there's a fixed header followed by data).

The data format from the datasheet.
The data format, from the datasheet.

I did GooTrans one thing, and that was the text "该电路中采用 5104 编码方式" which translates as "The circuit uses the 5104 encoding method" - and DuckDuckGo drew a blank on what "5104" is supposed to be. I've described how it works pretty well in this document, but if it has a normal name (you know, like "the NEC code"), I've not been able to find it.

 

Musings on oscilloscopes

This is an outline of how to throw together a cheap oscilloscope, not a tutorial. It's because I was asked how those really cheap ones actually work.

The truth is kind of horrible, but it explains why they flake out over a couple of hundred kilohertz.

Ready?

They use the microcontroller's ADC. That is to say, most microcontrollers have a little widget built in that can convert an analogue voltage, say in the range of 0 to 3V (or whatever) into a value in varying degrees of accuracy. Often only eight bit, so a number between 0 and 255, but if you are using a screen with only 240 pixels horizontally, then that's more than good enough.

An oscilloscope is simply a tool to visualise an electric signal over a duration of time, so simply run the ADC as fast as it'll go and keep taking lots of samples of what the voltage is to plot onto the display device, and, well, that's an oscilloscope. Not a good one, but even doing something as lame sounding as that can show you things your multimeter can't.

A lot of the rest of the hardware is a combination of input protection and ways to scale down the input. For instance if you wanted to have a "divide by ten" option to allow you to measure up to 30V (if the input is 0-3V), then you would use a potential divider. That is to say your signal is connected to ground through a 100KΩ resistor and a 10KΩ resistor in series, and you take the signal from between the two. So there's 100 kilo ohms between input and what you're feeding into the ADC, but only 10 kilo ohms between the ADC feed and ground, which makes a 10:1 ratio, or a divide-by-ten. This can then be fixed in software to simply multiply everything on-screen by ten.
For handling AC, just have a switch that either passes the signal through as-is (DC) or pushes it through a low rated (0.1µF, for example) ceramic capacitor. This performs capacitive coupling so AC can get through but DC is blocked.

There is a bit more to it than that (as even cheap oscilloscopes support a range of input voltages), but that's the basic premise: Take whatever the input is, scale it down to something that won't fry the ADC, then read it loads of times really quickly to sample the input, and bung the results onto a screen. As long as you don't need it to work at high frequencies, this is all within the range of a modern microcontroller. What I did above ran at 38kHz, I would imagine even an ESP32 can sample fast enough to be useful for that sort of task.

How better quality devices can offer improved performance, such as my new BSide device, is to have a dedicated chip for the sampling. This then gets fed into a FIFO (or FPGA acting as one) to store and buffer all of the samples for the microcontroller (probably some sort of STM32 ARM device) to read out and use. In this way the actual sampling can run a hell of a lot faster, and nothing depends upon how fast the processor can do stuff.

Much better quality devices have a higher resolution display, and accordingly can sample at higher resolutions, say turning a voltage into 0-4095 which is far better than the 0-255 of a simple eight bit device. They can also sample at much higher speeds, so your 100MHz is a nice solid 100MHz and not the "mathematically optimistic" speeds quoted on cheaper devices (like my new one claiming 10MHz). Multiple inputs are a given. Plus lots of knobs and buttons so you aren't stuck fighting a weird user interface.
Of course, it goes without saying, that the quality of the parts inside will improve as the price goes up. Maybe your resistor's after-gap band will be grey (0.05%) rather than silver (10%). That's the tolerance, the "how close this resistor is to what it claims to be". The greater the tolerance of the components, the less accurate the equipment will be.
And that is why a decent 'scope carries a hefty price tag.

 

Why do the specs lie? Because it sells. You'll look at the 10MHz and think it's better than a 5MHz model even though they're both running at 48Msamples/sec.

Let's break this down. The maximum speed the thing can run at is 48MS/s divided by two (the Nyquist limit). This means it can theoretically have 24MHz of usable bandwidth. But this is simply the practical limit of being able to detect a sine wave without aliasing. But we're talking real-world rather than cosy sine waves. So if we're running the ADC at 15 samples per point, this gives us a good measurement, but it means that anything over 3.2MHz is going to suffer. Trying to get the claimed 10MHz means 4.8 samples per point (probably rounded down to four) which is, well, kind of gnarly and hideous.
But the real limitation is not necessarily the ADC, it's probably cheap op-amps and parts used in the front-end (the circuitry before the ADC) which means the analogue bandwidth is going to attenuate the signal as the frequency goes up.

Of course, there's a little nugget of truth lurking in the specs that say the rise time is "less than 100ns". Even old eight bit micros can have rise times faster than that - the BBC's 1MHz bus has a read hold time of 30ns, and a write hold time of 50ns (as is expected with the 6502); but generally this doesn't matter because the device is good enough for the sort of things I'm interested in: Is there a clock signal here? Does this address line look correct or is it stuck? Is the chip select pulsing? And, of course, pulling apart an IR signal by looking at the signal itself.

 

I grew up in an era of cathode ray scopes, so I'm quite at home with this sort of thing. The irony, of course, is that even though it has a bandwidth that would be quite happy with 2MHz, it would not be overly useful for this sort of task because images only stay on the screen for as long as they are happening. I could trigger the waveform and the carrier wave without problem, but counting the carrier cycles might present quite the problem. You could get "storage scopes", which were absolute marvels of engineering, and came with a price tag to match.
Either way, I think what will be the hardest part of the OT3 to deal with (asides from the really peculiar triggering and the redraw bugs) will be the arcane menu system. Because this device, costing little and being small, is optimised to use a few buttons as possible.

The big question, of course, is why? Why bother with this odd little gadget? Well, if you're doing any work with electronics, there's going to come a time when no amount of multimeter-fu is going to tell you what you need to know. An oscilloscope, at a painfully rough description, is a few hundred rapid multimeter readings at once. You can see the voltage, and when you're dealing with echoes in a long cable or attenuated data signals, a multimeter won't show you that (it isn't designed to). A 'scope will. You can see from the above the sort of things that can be done when you can see the signal. That's why.

 

 

Your comments:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺
As of February 2025, commenting is no longer available to UK residents, following the implementation of the vague and overly broad Online Safety Act. You must tick the box below to verify that you are not a UK resident, and you expressly agree if you are in fact a UK resident that you will indemnify me (Richard Murray), as well as the person maintaining my site (Rob O'Donnell), the hosting providers, and so on. It's a shitty law, complain to your MP.
It's not that I don't want to hear from my British friends, it's because your country makes stupid laws.

 
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

Zerosquare, 15th February 2026, 01:24
If you don't have a fancy scope, a handy trick to analyze IR signals is to use... the line-in input on a sound card (with a resistor divider to lower the amplitude to a few hundred millivolts). Then you can use any audio editor to pan and zoom ; the carrier may or may not be visible, depending on the sample rate, but the modulation will show up nicely. You can even extract the data automatically with a bit of code.
David Pilling, 15th February 2026, 18:49
The BA5104 (and similar SW5104) is a CMOS infrared remote control encoder chip that uses a specific, 12-bit pulse modulation encoding method to generate a 38 kHz infrared carrier signal. It is commonly used in consumer electronics, such as fan remote controls, for transmitting commands.  
Encoding and Modulation Details 
Frame Structure: Each transmission consists of a 12-bit code, including 3 start bits, 2 user/address code bits, and 7 command code bits. 
Encoding Scheme: The chip uses pulse distance modulation, where the timing of the infrared pulses represents logical "0"s and "1"s. 
Logic "0": Represented by a short high-level pulse (1/4T) followed by a long low-level gap (3/4T). 
Logic "1": Represented by a long high-level pulse (3/4T) followed by a short low-level gap (1/4T). 
Timing (T): The period (T) of each code bit is approximately 1.6879 ms. 
Carrier Frequency: The chip generates a 38 kHz carrier signal by using a 455 kHz crystal oscillator and dividing it by 12. 
Operation: When a button is pressed, the chip oscillates, modulates the button data, and outputs a serialized signal from pin 15 to an external transistor (like a Darlington pair) for IR LED driving.  
Key Features 
Channels: It has 8 input channels. 
User Codes: It supports 2-digit user code settings, allowing for 32 different combinations to avoid interference between similar devices. 
Voltage Range: Operates over a wide range of 2.0V to 5.0V.  
Decoding Method 
The BA5104 signal can be decoded using software methods on microcontrollers (e.g., AVR) using either external interrupts or input capture methods, with input capture generally providing better results.
David Pilling, 15th February 2026, 18:59
Pity modern phones don't have ir receivers, or else your could use irplus learn mode - which would have spoiled an interesting discussion. 
 
Modern o'scopes are a mix of software and hardware, and there is many a one where the developers do decent hardware but fail on the software side. Maybe that is true of other things. 
 
Rick, 15th February 2026, 19:50
It's sort of a running theme around here that "nice hardware, shame the firmware is abominable". 
 
I've come to the conclusion several times that nobody on the development team actually uses "thing", because if they did, they wouldn't have left it in that state, the state where it technically functions, but.....
C Ferris, 15th February 2026, 20:56
I see there is a O'scope article by Mike Cook in a Pi Mag.
David Pilling, 15th February 2026, 22:15
Mike Cook uses an Arduino and a Pi for his O'scope. Bog standard Arduino does not have a very fast A to D. The article says he looks at a 1MHz signal, does not mean he sees anything. 
 
That thing I copied above: 
"2-digit user code settings, allowing for 32 different combinations" - AI getting 32 out of 2 bits. 

Add a comment (v0.12) [help?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 06614 backwards.
UK resident
Your comment
French flagSpanish flagJapanese flag
Calendar
«   February 2026   »
MonTueWedThuFriSatSun
      
3456
1011121315
1920
23242627 

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index

Geekery
 
Alphabetical:

Search

Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.

Etc...

Last read at 13:20 on 2026/03/07.

QR code


Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0

 

© 2026 Rick Murray
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.

 

Have you noticed the watermarks on pictures?
Next entry - 2026/02/16
Return to top of page