User Tools

Site Tools





Lacrosse TX3

Lacrosse TX3 is a protocol used by some weather stations to communicate with an external temperature sensor. I started exploring this subject when my temperature sensor had passed away and I decided to build my own. Unfortunately I didn't have an opportunity to sniff the communication between the weather station and the temperature sensor, but I was lucky, because it didn't take me long to find similar product on the Internet with the protocol explained.

I've found two pages that were very helpful. The first one describes details of Lacrosse TX3 datagrams, and the second one gives hints on how to send them. Unfortunately the description of the protocol on the first page has a mistake, or my weather station is more Chinese than I thought, thus I had to modify it a little to work properly.

Protocol description

Although you can find all information on the linked sites, I decided to rewrite them here, in case my website will be the only knowledge base after III World War.


Each datagram consists of 44 bits:

  • 8 bits - preamble
  • 4 bits - type of measure
  • 7 bits - device address
  • 1 bit - parity
  • 12 bits - temperature
  • 8 bits - repeated temperature
  • 4 bits - checksum


It's always 0x0A, no matter what.


According to the linked website, this is a type of measure. It can be 0x0 for temperature or 0xE for humidity. I can't verify humidity measurement, because my weather station is cheap, but I'm 100% sure that 0x0 works for temperature.


Address of the sensor. It's not described how weather stations should interpret this value, but I can say a few words about mine. When my weather station is firstly powered on, it waits for any transmission for about 90 seconds, and then it remembers address of every sensor (up to 4) that sent something. Depending on how many of them sent something during this time, my weather station allows me to switch between those readings. The station repeats waiting for readings every 2 or 3 minutes, but now it ignores all transmitters that weren't learned on the beginning.


I had problem with this. I used formula from the linked website but it didn't work. After about 2 hours of analyzing I eventually came up with a valid method of calculating this. I'm not sure whether this is a problem with my Chinese weather station, or author of the website made a mistake, but it's better to have more possible methods that none, right? So if my formula doesn't work for you, use that from the website.

Parity bit works as a typical parity bit. It should be set when number of 1's in the temperature part is odd. So if you sum 1's from the pink and yellow part it should be even. Don't include repeated temperature or address.


Temperature is encoded on 12 bits as a packed BCD. That means every nibble (4 bits) is a part of the resulting decimal, with the last digit being a fraction.

0111 0011 0001 = 0x731

 7    3  . 1

Resulting temperature is 73.1 degrees. Pretty hot, isn't it? To get a valid value you have to subtract 50 from the reading and that's it - 23.1 degrees in Celsius scale. I'm not sure about origins of this offset, but I can only suppose that it's a method of transmitting negative values. Every reading below 50 will result in a negative number, thus the minimum temperature we can transmit is -50 degrees (reading equals to 0).

Repeated temperature

I don't know why it's there, but it should be equal to the first 8 bits of the temperature.


Checksum is calculated by summing all nibbles together and then ANDing the sum with 0x0F. In the above example it will be:

0000  1010  0000  0000  1110  0111  0011  0001  0111  0011

0x0 + 0xA + 0x0 + 0x0 + 0xE + 0x7 + 0x3 + 0x1 + 0x7 + 0x3 = 0x2D

0x2D & 0x0F = 0xD = 1101


If we take a look at the linked site, we'll see that data is sent on 433.92 MHz using OOK modulation, and bits are encoded using PWM. The bit times given on that site are too detailed and we can optimize it a little.

Each transmitted bit consists of two parts: a variable-length high state and a fixed-length low state. I think the table below will explain everything.

High state Low state
0 1300 us 1000 us
1 500 us 1000 us

Datagrams are transmitted MSB first. I also send them twice, with 500 ms interval, I don't know whether this is required, but I saw that my weather station works better if I do so.

Transmission test

I've built a prototype temperature transmitter on Nucleo F103RB and RTX-MID-3V transceiver. Using a 32-bit MCU for this project looks like an overkill, but it's only for testing purposes. The final transmitter can be easily done even on a small ATtiny. I'm not going to describe the whole process of setting up the project, as you can read about that in another article. You can just clone my GitHub repo and you're good to go.

The next thing is wiring up a Nucleo and a transceiver:

I'm a lazy duck, and I had neither will nor time to create a graphical representation of RTX transceiver, so it's just a dummy part on the schematic. The pins - counting from top - are:

  1. Antenna - it's just a hanging wire, it's enough for testing
  2. Ground
  3. Not connected
  4. Data input (connected to PA0)
  5. TX/RX switch (connected to PA1)
  6. Enable / Disable (connected to PA4)
  7. Ground again
  8. Analog output
  9. Data output
  10. VCC

Basically that's all from the hardware part of this project. Now it's time to do some…


I'm a lazy duck, I have a messy room, but I hate messy codes. That's the main reason I split my project to separate modules:

  • Timer - it's for delays,
  • RTX - transceiver driver,
  • Lacrosse - protocol driver.

I won't paste and describe every part of these files, because it can be easily cloned from my repo, I'd like to focus only on the most interesting parts.


I utilized 2 timers for microsecond and millisecond delays. The setup method initializes two timers in their basic counting mode. Delay function simply resets timer counter and waits until it exceeds a given value.

void timer_setup() {
    TIM1->PSC = (SystemCoreClock / 1000000) - 1;
    TIM1->EGR |= TIM_EGR_UG;
    TIM1->CR1 |= TIM_CR1_CEN;
    TIM2->PSC = (SystemCoreClock / 1000) - 1;
    TIM2->EGR |= TIM_EGR_UG;
    TIM2->CR1 |= TIM_CR1_CEN;
static inline void timer_delay_us(uint16_t delay) {
    TIM1->CNT = 0;
    while(TIM1->CNT < delay);
static inline void timer_delay_ms(uint16_t delay) {
    TIM2->CNT = 0;
    while(TIM2->CNT < delay);

This article is in progress

hardware/lacrosse_tx3.txt · Last modified: 2019/08/02 13:29 by itachi