I bought a wind direction sensor from china, it’s a spare part for the “misol” weather stations, just search for “wind direction sensor misol aliexpress” for the product, it was less than 15€.



I unscrewed it to understand how it works. The picture of the PCB verifies that the hardware matches the datasheets I found in this wiki:
datasheet_1
datasheet_2
So it’s a few hall sensors that switch resistors connected in parallel. The magnet switches one or two of them at once:

The resistor values are given in the datasheet:
Direction (degrees) | Resistance (ohms) |
---|---|
0 | 33K |
22.5 | 6.57K |
45 | 8.2K |
67.5 | 891 |
90 | 1K |
112.5 | 688 |
135 | 2.2K |
157.5 | 1.41K |
180 | 3.9K |
202.5 | 3.14K |
225 | 16K |
247.5 | 14.12K |
270 | 120K |
292.5 | 42.12K |
315 | 64.9K |
337.5 | 21.88K |
Every second entry in this table is a combination of the entries below and above when both switches are triggered and is defined by the formular for two resistors connected in parallel:
$$R = \frac{R_1 \cdot R_2}{R_1 + R_2}$$
So we have to measure the resistance of the sensor via the ADC. Therefore a voltage divider is needed. A voltage divider looks like this:

R2 is the variable resistor that is the sensor. R1 has to be chosen correctly so that it matches the range of R2, the value of Vin and the desired measuring range on Vout.
According to the datasheets we have to measure resistances for R2 that can be between 688 Ω and 120 kΩ. The ADC of the ESP32S3 accepts up to 3100 mV with ADC_ATTEN_DB_11 (that’s Vout), Vin is 3.3V from the ESP32.
Now the value for R1 has to be calculated for the biggest possible resistor, so we have R2=120 kΩ, Vin=3.3V and Vout≤3.1V:
The formular for the voltage divider is:
$$V_{\text{out}} = V_{\text{in}} \cdot \frac{R_2}{R_1 + R_2}$$
So, for our values of Vin, Vout and R2 we get:
$$R_1 = R_2 \cdot (\frac{V_{\text{in}}}{V_{\text{out}}} – 1) = 120kΩ \cdot (\frac{3.3V}{3.1V} – 1) ≈ 7.74kΩ$$
The next fitting standard resistor is 8.2kΩ, let’s calculate Vout for it:
$$V_{\text{out}} = V_{\text{in}} \cdot \frac{R_2}{R_1 + R_2} = 3.3V \cdot \frac{120kΩ}{8.2kΩ + 120kΩ} ≈ 3089mV$$
That’s a good value: with it we get 3.089V on Vout for 120kΩ which is about 99.6% of the maximal voltage of 3.1V the ADC of the ESP323 can handle.
The ADC has 12 bits, that means we have a measuring range of 212=4096 values, 99.6% of it are ≈4080, so we have a resolution of round about 120kΩ/4080 ≈ 29Ω.
That’s sufficient, the minimal difference between the resistors we want to measure is more than 100Ω.
So we can wire the sensor up now via a properly chosen voltage divider:

Integrating it in esphome is comfortable:
substitutions:
update_interval: 5s
text_sensor:
- platform: template
name: "Wind direction"
icon: 'mdi:windsock'
id: wind_dir
sensor:
- platform: adc
id: source_sensor
pin: GPIO32
name: ADC
attenuation: 11db
internal: true
update_interval: ${update_interval}
accuracy_decimals: 1
- platform: resistance
sensor: source_sensor
id: resistance_sensor
configuration: DOWNSTREAM
resistor: 8.2kOhm
internal: true
name: Resistance Sensor
reference_voltage: 3.3V
accuracy_decimals: 1
update_interval: ${update_interval}
on_value:
then:
- lambda: |-
struct WindEntry {
int lower;
int upper;
const char* direction;
float heading;
};
static const WindEntry wind_table[] = {
{ 400, 789, "East-South-East", 112.5}, // 688 Ω
{ 789, 945, "East-North-East", 67.5}, // 891 Ω
{ 945, 1205, "East", 90.0}, // 1 kΩ
{ 1205, 1805, "South-South-East", 157.5}, // 1.41 kΩ
{ 1805, 2670, "South-East", 135.0}, // 2.2 kΩ
{ 2670, 3520, "South-South-West", 202.5}, // 3.14 kΩ
{ 3520, 5235, "South", 180.0}, // 3.9 kΩ
{ 5235, 7385, "North-North-East", 22.5}, // 6.57 kΩ
{ 7385, 11160, "North-East", 45.0}, // 8.2 kΩ
{11160, 15060, "West-South-West", 247.5}, // 14.12 kΩ
{15060, 18940, "South-West", 225.0}, // 16 kΩ
{18940, 27440, "North-North-West", 337.5}, // 21.88 kΩ
{27440, 37560, "North", 0.0}, // 33 kΩ
{37560, 53510, "West-North-West", 292.5}, // 42.12 kΩ
{53510, 92450, "North-West", 315.0}, // 64.9 kΩ
{92450, 150000, "West", 270.0}, // 120 kΩ
};
const int resistance = id(resistance_sensor).state;
for (const auto& entry : wind_table) {
if (resistance >= entry.lower && resistance < entry.upper) {
id(wind_dir).publish_state(entry.direction);
id(wind_heading).publish_state(entry.heading);
break;
}
}
- platform: template
name: "Wind Heading"
id: wind_heading
unit_of_measurement: "°"
Leave a Reply