@ -0,0 +1,9 @@ |
||||
html: |
||||
pandoc -s weather_station.md --toc --toc-depth=1 -c ./css/mvp.css \
|
||||
--template template.html -o weather_station.html
|
||||
|
||||
pdf: |
||||
pandoc weather_station.md --toc -o weather_station.pdf --pdf-engine=xelatex
|
||||
|
||||
clean: |
||||
rm -rvf build
|
@ -0,0 +1,563 @@ |
||||
/* MVP.css v1.14 - https://github.com/andybrewer/mvp */ |
||||
/* Edited by Konstapel https://meezenest.nl/mees */ |
||||
|
||||
:root { |
||||
--active-brightness: 0.85; |
||||
--border-radius: 5px; |
||||
--box-shadow: 2px 2px 10px; |
||||
--color-accent: #f4f0ec; |
||||
--color-bg: #fff; |
||||
--color-bg-secondary: #e9e9e9; |
||||
--color-link: #a9a9a9; |
||||
--color-secondary: #3366FF; |
||||
--color-secondary-accent: #920de90b; |
||||
--color-shadow: #f4f4f4; |
||||
--color-table: #a9a9a9; |
||||
--color-text: #000; |
||||
--color-text-secondary: #999; |
||||
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; |
||||
--hover-brightness: 1.2; |
||||
--justify-important: center; |
||||
--justify-normal: left; |
||||
--line-height: 1.5; |
||||
--width-card: 285px; |
||||
--width-card-medium: 460px; |
||||
--width-card-wide: 1080px; |
||||
--width-content: 1080px; |
||||
} |
||||
|
||||
@media (prefers-color-scheme: dark) { |
||||
:root[color-mode="user"] { |
||||
--color-accent: #0097fc4f; |
||||
--color-bg: #333; |
||||
--color-bg-secondary: #555; |
||||
--color-link: #0097fc; |
||||
--color-secondary: #e20de9; |
||||
--color-secondary-accent: #e20de94f; |
||||
--color-shadow: #bbbbbb20; |
||||
--color-table: #0097fc; |
||||
--color-text: #f7f7f7; |
||||
--color-text-secondary: #aaa; |
||||
} |
||||
} |
||||
|
||||
html { |
||||
scroll-behavior: smooth; |
||||
} |
||||
|
||||
@media (prefers-reduced-motion: reduce) { |
||||
html { |
||||
scroll-behavior: auto; |
||||
} |
||||
} |
||||
|
||||
/* Layout */ |
||||
article aside { |
||||
background: var(--color-secondary-accent); |
||||
border-left: 4px solid var(--color-secondary); |
||||
padding: 0.01rem 0.8rem; |
||||
} |
||||
|
||||
body { |
||||
background: var(--color-bg); |
||||
color: var(--color-text); |
||||
font-family: var(--font-family); |
||||
line-height: var(--line-height); |
||||
margin: 0; |
||||
overflow-x: hidden; |
||||
padding: 0; |
||||
font-size: 110%; |
||||
} |
||||
|
||||
footer, |
||||
header, |
||||
main { |
||||
margin: 0 auto; |
||||
max-width: var(--width-content); |
||||
padding: 3rem 1rem; |
||||
} |
||||
|
||||
main { |
||||
border: 1px solid lightgray; |
||||
border-radius: 5px; |
||||
} |
||||
|
||||
footer { |
||||
font-size: 0.7em; |
||||
} |
||||
|
||||
hr { |
||||
background-color: var(--color-bg-secondary); |
||||
border: none; |
||||
height: 1px; |
||||
margin: 4rem 0; |
||||
width: 100%; |
||||
} |
||||
|
||||
section { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
justify-content: var(--justify-important); |
||||
} |
||||
|
||||
section img, |
||||
article img { |
||||
max-width: 100%; |
||||
} |
||||
|
||||
section pre { |
||||
overflow: auto; |
||||
} |
||||
|
||||
section aside { |
||||
border: 1px solid var(--color-bg-secondary); |
||||
border-radius: var(--border-radius); |
||||
box-shadow: var(--box-shadow) var(--color-shadow); |
||||
margin: 1rem; |
||||
padding: 1.25rem; |
||||
width: var(--width-card); |
||||
} |
||||
|
||||
section aside:hover { |
||||
box-shadow: var(--box-shadow) var(--color-bg-secondary); |
||||
} |
||||
|
||||
[hidden] { |
||||
display: none; |
||||
} |
||||
|
||||
/* Headers */ |
||||
article header, |
||||
div header, |
||||
main header { |
||||
padding-top: 0; |
||||
} |
||||
|
||||
header { |
||||
text-align: var(--justify-important); |
||||
} |
||||
|
||||
header a b, |
||||
header a em, |
||||
header a i, |
||||
header a strong { |
||||
margin-left: 0.5rem; |
||||
margin-right: 0.5rem; |
||||
} |
||||
|
||||
header nav img { |
||||
margin: 1rem 0; |
||||
} |
||||
|
||||
section header { |
||||
padding-top: 0; |
||||
width: 100%; |
||||
} |
||||
|
||||
/* Nav */ |
||||
nav { |
||||
align-items: center; |
||||
display: flex; |
||||
font-weight: bold; |
||||
justify-content: space-between; |
||||
margin-bottom: 7rem; |
||||
} |
||||
|
||||
nav ul { |
||||
list-style: none; |
||||
padding: 0; |
||||
} |
||||
|
||||
nav ul li { |
||||
display: inline-block; |
||||
margin: 0 0.5rem; |
||||
position: relative; |
||||
text-align: left; |
||||
} |
||||
|
||||
/* Nav Dropdown */ |
||||
nav ul li:hover ul { |
||||
display: block; |
||||
} |
||||
|
||||
nav ul li ul { |
||||
background: var(--color-bg); |
||||
border: 1px solid var(--color-bg-secondary); |
||||
border-radius: var(--border-radius); |
||||
box-shadow: var(--box-shadow) var(--color-shadow); |
||||
display: none; |
||||
height: auto; |
||||
left: -2px; |
||||
padding: .5rem 1rem; |
||||
position: absolute; |
||||
top: 1.7rem; |
||||
white-space: nowrap; |
||||
width: auto; |
||||
z-index: 1; |
||||
} |
||||
|
||||
nav ul li ul::before { |
||||
/* fill gap above to make mousing over them easier */ |
||||
content: ""; |
||||
position: absolute; |
||||
left: 0; |
||||
right: 0; |
||||
top: -0.5rem; |
||||
height: 0.5rem; |
||||
} |
||||
|
||||
nav ul li ul li, |
||||
nav ul li ul li a { |
||||
display: block; |
||||
} |
||||
|
||||
/* Typography */ |
||||
code, |
||||
samp { |
||||
background-color: var(--color-accent); |
||||
border-radius: var(--border-radius); |
||||
color: var(--color-text); |
||||
display: inline-block; |
||||
margin: 0 0.1rem; |
||||
padding: 0 0.5rem; |
||||
} |
||||
|
||||
details { |
||||
margin: 1.3rem 0; |
||||
} |
||||
|
||||
details summary { |
||||
font-weight: bold; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
h1, |
||||
h2, |
||||
h3, |
||||
h4, |
||||
h5, |
||||
h6 { |
||||
line-height: var(--line-height); |
||||
} |
||||
|
||||
h1 { |
||||
font-size: 1.875em; |
||||
border-bottom: 1px solid lightgray; |
||||
} |
||||
|
||||
h2 { |
||||
font-size: 1.4em; |
||||
} |
||||
|
||||
mark { |
||||
padding: 0.1rem; |
||||
} |
||||
|
||||
ol li, |
||||
ul li { |
||||
padding: 0.2rem 0; |
||||
} |
||||
|
||||
p { |
||||
margin: 0.75rem 0; |
||||
padding: 0; |
||||
width: 100%; |
||||
} |
||||
|
||||
pre { |
||||
margin: 1rem 0; |
||||
max-width: var(--width-card-wide); |
||||
padding: 1rem 0; |
||||
} |
||||
|
||||
pre code, |
||||
pre samp { |
||||
display: block; |
||||
max-width: var(--width-card-wide); |
||||
padding: 0.8rem 2rem; |
||||
/*white-space: pre-wrap;*/ |
||||
overflow-x: auto; |
||||
} |
||||
|
||||
small { |
||||
color: var(--color-text-secondary); |
||||
} |
||||
|
||||
sup { |
||||
background-color: var(--color-secondary); |
||||
border-radius: var(--border-radius); |
||||
color: var(--color-bg); |
||||
font-size: xx-small; |
||||
font-weight: bold; |
||||
margin: 0.2rem; |
||||
padding: 0.2rem 0.3rem; |
||||
position: relative; |
||||
top: -2px; |
||||
} |
||||
|
||||
/* Links */ |
||||
a { |
||||
color: var(--color-link); |
||||
display: inline-block; |
||||
font-weight: bold; |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
a:active { |
||||
filter: brightness(var(--active-brightness)); |
||||
} |
||||
|
||||
a:hover { |
||||
filter: brightness(var(--hover-brightness)); |
||||
} |
||||
|
||||
a b, |
||||
a em, |
||||
a i, |
||||
a strong, |
||||
button, |
||||
input[type="submit"] { |
||||
border-radius: var(--border-radius); |
||||
display: inline-block; |
||||
font-size: medium; |
||||
font-weight: bold; |
||||
line-height: var(--line-height); |
||||
margin: 0.5rem 0; |
||||
padding: 1rem 2rem; |
||||
} |
||||
|
||||
button, |
||||
input[type="submit"] { |
||||
font-family: var(--font-family); |
||||
} |
||||
|
||||
button:active, |
||||
input[type="submit"]:active { |
||||
filter: brightness(var(--active-brightness)); |
||||
} |
||||
|
||||
button:hover, |
||||
input[type="submit"]:hover { |
||||
cursor: pointer; |
||||
filter: brightness(var(--hover-brightness)); |
||||
} |
||||
|
||||
a b, |
||||
a strong, |
||||
button, |
||||
input[type="submit"] { |
||||
background-color: var(--color-link); |
||||
border: 2px solid var(--color-link); |
||||
color: var(--color-bg); |
||||
} |
||||
|
||||
a em, |
||||
a i { |
||||
border: 2px solid var(--color-link); |
||||
border-radius: var(--border-radius); |
||||
color: var(--color-link); |
||||
display: inline-block; |
||||
padding: 1rem 2rem; |
||||
} |
||||
|
||||
article aside a { |
||||
color: var(--color-secondary); |
||||
} |
||||
|
||||
/* Images */ |
||||
figure { |
||||
margin: 0; |
||||
padding: 0; |
||||
text-align: center; |
||||
} |
||||
|
||||
figure img { |
||||
max-width: 100%; |
||||
max-height: 500px; |
||||
} |
||||
|
||||
figure figcaption { |
||||
color: var(--color-text-secondary); |
||||
} |
||||
|
||||
/* Forms */ |
||||
button:disabled, |
||||
input:disabled { |
||||
background: var(--color-bg-secondary); |
||||
border-color: var(--color-bg-secondary); |
||||
color: var(--color-text-secondary); |
||||
cursor: not-allowed; |
||||
} |
||||
|
||||
button[disabled]:hover, |
||||
input[type="submit"][disabled]:hover { |
||||
filter: none; |
||||
} |
||||
|
||||
form { |
||||
border: 1px solid var(--color-bg-secondary); |
||||
border-radius: var(--border-radius); |
||||
box-shadow: var(--box-shadow) var(--color-shadow); |
||||
display: block; |
||||
max-width: var(--width-card-wide); |
||||
min-width: var(--width-card); |
||||
padding: 1.5rem; |
||||
text-align: var(--justify-normal); |
||||
} |
||||
|
||||
form header { |
||||
margin: 1.5rem 0; |
||||
padding: 1.5rem 0; |
||||
} |
||||
|
||||
input, |
||||
label, |
||||
select, |
||||
textarea { |
||||
display: block; |
||||
font-size: inherit; |
||||
max-width: var(--width-card-wide); |
||||
} |
||||
|
||||
input[type="checkbox"], |
||||
input[type="radio"] { |
||||
display: inline-block; |
||||
} |
||||
|
||||
input[type="checkbox"]+label, |
||||
input[type="radio"]+label { |
||||
display: inline-block; |
||||
font-weight: normal; |
||||
position: relative; |
||||
top: 1px; |
||||
} |
||||
|
||||
input[type="range"] { |
||||
padding: 0.4rem 0; |
||||
} |
||||
|
||||
input, |
||||
select, |
||||
textarea { |
||||
border: 1px solid var(--color-bg-secondary); |
||||
border-radius: var(--border-radius); |
||||
margin-bottom: 1rem; |
||||
padding: 0.4rem 0.8rem; |
||||
} |
||||
|
||||
input[type="text"], |
||||
textarea { |
||||
width: calc(100% - 1.6rem); |
||||
} |
||||
|
||||
input[readonly], |
||||
textarea[readonly] { |
||||
background-color: var(--color-bg-secondary); |
||||
} |
||||
|
||||
label { |
||||
font-weight: bold; |
||||
margin-bottom: 0.2rem; |
||||
} |
||||
|
||||
/* Popups */ |
||||
dialog { |
||||
border: 1px solid var(--color-bg-secondary); |
||||
border-radius: var(--border-radius); |
||||
box-shadow: var(--box-shadow) var(--color-shadow); |
||||
position: fixed; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
width: 50%; |
||||
z-index: 999; |
||||
} |
||||
|
||||
/* Tables */ |
||||
table { |
||||
border: 1px solid var(--color-bg-secondary); |
||||
border-radius: var(--border-radius); |
||||
border-spacing: 0; |
||||
display: inline-block; |
||||
max-width: 100%; |
||||
overflow-x: auto; |
||||
padding: 0; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
table td, |
||||
table th, |
||||
table tr { |
||||
padding: 0.4rem 0.8rem; |
||||
text-align: var(--justify-important); |
||||
} |
||||
|
||||
table thead { |
||||
background-color: var(--color-table); |
||||
border-collapse: collapse; |
||||
border-radius: var(--border-radius); |
||||
/*color: var(--color-bg);*/ |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
table thead th:first-child { |
||||
border-top-left-radius: var(--border-radius); |
||||
} |
||||
|
||||
table thead th:last-child { |
||||
border-top-right-radius: var(--border-radius); |
||||
} |
||||
|
||||
table thead th:first-child, |
||||
table tr td:first-child { |
||||
text-align: var(--justify-normal); |
||||
} |
||||
|
||||
table tr:nth-child(even) { |
||||
background-color: var(--color-accent); |
||||
} |
||||
|
||||
/* Quotes */ |
||||
blockquote { |
||||
display: block; |
||||
font-size: x-large; |
||||
line-height: var(--line-height); |
||||
margin: 1rem auto; |
||||
max-width: var(--width-card-medium); |
||||
padding: 1.5rem 1rem; |
||||
text-align: var(--justify-important); |
||||
} |
||||
|
||||
blockquote footer { |
||||
color: var(--color-text-secondary); |
||||
display: block; |
||||
font-size: small; |
||||
line-height: var(--line-height); |
||||
padding: 1.5rem 0; |
||||
} |
||||
|
||||
/* Scrollbars */ |
||||
* { |
||||
scrollbar-width: thin; |
||||
scrollbar-color: rgb(202, 202, 232) auto; |
||||
} |
||||
|
||||
*::-webkit-scrollbar { |
||||
width: 5px; |
||||
height: 5px; |
||||
} |
||||
|
||||
*::-webkit-scrollbar-track { |
||||
background: transparent; |
||||
} |
||||
|
||||
*::-webkit-scrollbar-thumb { |
||||
background-color: rgb(202, 202, 232); |
||||
border-radius: 10px; |
||||
} |
||||
|
||||
h1.title { |
||||
border-bottom: 0px solid white; |
||||
} |
||||
|
After Width: | Height: | Size: 205 KiB |
After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 238 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 586 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 44 KiB |
@ -0,0 +1,103 @@ |
||||
<!DOCTYPE html> |
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<meta name="generator" content="pandoc" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> |
||||
$for(author-meta)$ |
||||
<meta name="author" content="$author-meta$" /> |
||||
$endfor$ |
||||
$if(date-meta)$ |
||||
<meta name="dcterms.date" content="$date-meta$" /> |
||||
$endif$ |
||||
$if(keywords)$ |
||||
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" /> |
||||
$endif$ |
||||
<title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title> |
||||
$if(highlighting-css)$ |
||||
<style type="text/css"> |
||||
$highlighting-css$ |
||||
</style> |
||||
$endif$ |
||||
$for(css)$ |
||||
<link rel="stylesheet" href="$css$" /> |
||||
$endfor$ |
||||
<style type="text/css"> |
||||
:root { |
||||
--width-content: 1080px; |
||||
} |
||||
|
||||
nav { |
||||
justify-content: space-around; |
||||
} |
||||
|
||||
$if(quotes)$ |
||||
q { quotes: "“" "”" "‘" "’"; } |
||||
$endif$ |
||||
</style> |
||||
$if(math)$ |
||||
$math$ |
||||
$endif$ |
||||
$for(header-includes)$ |
||||
$header-includes$ |
||||
$endfor$ |
||||
</head> |
||||
<body> |
||||
$for(include-before)$ |
||||
$include-before$ |
||||
$endfor$ |
||||
$if(title)$ |
||||
<header id="title-block-header"> |
||||
<nav id="$idprefix$TOC"> |
||||
<a href="https://www.meezenest.nl/mees/"><img alt="Logo" src="$logo$" height="70"></a> |
||||
$if(toc)$ |
||||
<ul> |
||||
<li><a href="#">Menu</a> |
||||
$table-of-contents$ |
||||
</li> |
||||
</ul> |
||||
$endif$ |
||||
</nav> |
||||
<h1 class="title">$title$</h1> |
||||
$if(subtitle)$ |
||||
<p class="subtitle">$subtitle$</p> |
||||
$endif$ |
||||
$for(author)$ |
||||
<p class="author">$author$</p> |
||||
$endfor$ |
||||
$if(date)$ |
||||
<p class="date">$date$</p> |
||||
$endif$ |
||||
$if(website)$ |
||||
<p><a href="$website$"><i>Back ↗</i></a></p> |
||||
$endif$ |
||||
</header> |
||||
$endif$ |
||||
<main> |
||||
<article> |
||||
$if(abstract)$ |
||||
<p><b>Abstract </b>$abstract$</p> |
||||
$endif$ |
||||
$body$ |
||||
$for(include-after)$ |
||||
$include-after$ |
||||
$endfor$ |
||||
<hr> |
||||
</article> |
||||
</main> |
||||
<footer> |
||||
<p>© |
||||
$if(date)$ |
||||
$date$ |
||||
$endif$ |
||||
$for(author)$ |
||||
$author$ |
||||
$endfor$ |
||||
$if(website)$ |
||||
<a href="$website$">$website$</a> |
||||
$endif$ |
||||
</p><p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>. |
||||
</p> |
||||
</footer> |
||||
</body> |
||||
</html> |
@ -0,0 +1,726 @@ |
||||
<!DOCTYPE html> |
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> |
||||
<head> |
||||
<meta charset="utf-8" /> |
||||
<meta name="generator" content="pandoc" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> |
||||
<meta name="author" content="M.T. Konstapel" /> |
||||
<meta name="dcterms.date" content="2024-01-15" /> |
||||
<title>Weather station</title> |
||||
<link rel="stylesheet" href="./css/mvp.css" /> |
||||
<style type="text/css"> |
||||
:root { |
||||
--width-content: 1080px; |
||||
} |
||||
|
||||
nav { |
||||
justify-content: space-around; |
||||
} |
||||
|
||||
</style> |
||||
</head> |
||||
<body> |
||||
<header id="title-block-header"> |
||||
<nav id="TOC"> |
||||
<a href="https://www.meezenest.nl/mees/"><img alt="Logo" src="./images/mees_logo.svg" height="70"></a> |
||||
<ul> |
||||
<li><a href="#">Menu</a> |
||||
<ul> |
||||
<li><a href="#why-do-you-need-a-weather-station" |
||||
id="toc-why-do-you-need-a-weather-station">Why do you need a |
||||
weather station?</a></li> |
||||
<li><a href="#what-should-a-weather-station-measure" |
||||
id="toc-what-should-a-weather-station-measure">What should a |
||||
weather station measure?</a></li> |
||||
<li><a href="#what-sensors-do-we-need" |
||||
id="toc-what-sensors-do-we-need">What sensors do we |
||||
need?</a></li> |
||||
<li><a |
||||
href="#what-to-use-for-communication-with-the-outside-world" |
||||
id="toc-what-to-use-for-communication-with-the-outside-world">What |
||||
to use for communication with the outside world?</a></li> |
||||
<li><a href="#what-else" id="toc-what-else">What |
||||
else?</a></li> |
||||
<li><a href="#theory-of-operation---hardware" |
||||
id="toc-theory-of-operation---hardware">Theory of operation |
||||
- Hardware</a></li> |
||||
<li><a href="#theory-of-operation---software" |
||||
id="toc-theory-of-operation---software">Theory of operation |
||||
- Software</a></li> |
||||
<li><a href="#schematic" |
||||
id="toc-schematic">Schematic</a></li> |
||||
<li><a href="#software-dependencies" |
||||
id="toc-software-dependencies">Software |
||||
dependencies</a></li> |
||||
<li><a href="#license" id="toc-license">License</a></li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
</nav> |
||||
<h1 class="title">Weather station</h1> |
||||
<p class="subtitle">with ModBus RTU interface</p> |
||||
<p class="author">M.T. Konstapel</p> |
||||
<p class="date">2024-01-15</p> |
||||
<p><a href="https://meezenest.nl/mees/"><i>Back ↗</i></a></p> |
||||
</header> |
||||
<main> |
||||
<article> |
||||
<p><b>Abstract </b><p>A weather station build around a SparkFun Weather |
||||
Meter Kit (SEN-15901). The temperature, humidity and pressure are |
||||
measured with I2C sensors housed in an RS1 Passive Radiation Shield from |
||||
Garni. The data can be read via an RS485 ModBus RTU interface. The main |
||||
processor is an Arduino Pro Mini (ATmega328P 5V@16MHz)</p></p> |
||||
<h1 id="why-do-you-need-a-weather-station">Why do you need a weather |
||||
station?</h1> |
||||
<p>Well, you don’t…because if you want to know the weather, you look on |
||||
your phone. So why bother than? Because since the beginning of time, |
||||
people are obsessed with the weather. When I was a child, my grandmother |
||||
was measuring the temperature and rainfall on a daily basis. My |
||||
grandfather had an allotment, so he also was very interested in the |
||||
weather. The first thing my father read in the newspaper was the weather |
||||
report and the last thing he watched in the television was… the weather |
||||
report. And every hour he listed to the weather report on the radio. If |
||||
he talked to someone he always started the conversation by talking about |
||||
the weather. And when I open a new browser window, it automatically |
||||
opens the weather page.</p> |
||||
<p>So the weather is fascinating and taking your own measurements is a |
||||
lot of fun.</p> |
||||
<h1 id="what-should-a-weather-station-measure">What should a weather |
||||
station measure?</h1> |
||||
<p>As my grandmother already measured temperature and rainfall, these |
||||
ones are mandatory. And for the rest I looked at the website of the |
||||
Dutch meteorological institute. They measure wind direction, average |
||||
wind speed of the last 10 minutes, maximum wind gust of the last 10 |
||||
minutes, rainfall of the last hour as well as the last 24 hours, |
||||
temperature, humidity and atmospheric pressure.</p> |
||||
<h3 id="measurements">Measurements</h3> |
||||
<ul> |
||||
<li>Wind direction</li> |
||||
<li>Wind speed (average of last 10 minutes)</li> |
||||
<li>Wind gust (last 10 minutes)</li> |
||||
<li>Rain fall (last hour)</li> |
||||
<li>Rain fall (last 24 hours)</li> |
||||
<li>Temperature</li> |
||||
<li>Humidity</li> |
||||
<li>Atmospheric pressure</li> |
||||
</ul> |
||||
<h1 id="what-sensors-do-we-need">What sensors do we need?</h1> |
||||
<h2 id="wind-and-rain">Wind and rain</h2> |
||||
<p>Measuring wind and rain is difficult. Well, not if you want to do it |
||||
by hand: place a beaker on the ground and wait a day. Than measure the |
||||
amount of water in it. Empty the beaker and start again. And for the |
||||
wind, you can stick a pole in the ground and attach a ribbon to it. The |
||||
direction of the wind can than be made visible. And even the wind speed |
||||
can be determent by measuring the angle between the ribbon and the |
||||
ground.</p> |
||||
<p>But how to do this automatically? Of course you can buy a fancy |
||||
commercial weather station. These are surprisingly cheap these days. But |
||||
that’s not a challenge. Besides, than you buy into a proprietary |
||||
ecosystem. And it probably only works when connected to the cloud. No |
||||
thanks!</p> |
||||
<p>Building from scratch is an option, but I am an electronic engineer, |
||||
not a mechanical one. I can imagine that won’t be a success. Besides |
||||
going the professional route, which is ridiculously expensive, there is |
||||
really only one option left: the SparkFun SEN-15901 Weather Meter.</p> |
||||
<figure> |
||||
<img src="./images/SparkFun-Weather_Meter.jpg" |
||||
title="SparkFun Weather Meter" alt="SparkFun Weather Meter" /> |
||||
<figcaption aria-hidden="true">SparkFun Weather Meter</figcaption> |
||||
</figure> |
||||
<p>But this contraption does not come with any signal conditioning. We |
||||
have to make some kind of interface. Luckily, Sparkfun provides an |
||||
Arduino library, so we only have to connect the SEN-15901 to an Arduino |
||||
and run the code.</p> |
||||
<h2 id="temperature-humidity-and-air-pressure">Temperature, humidity and |
||||
air pressure</h2> |
||||
<p>These three are easy: there are a lot of I2C chips capable of |
||||
measuring these parameters. I choose the Silicon Labs Si7021 for |
||||
humidity and temperature and the Bosch BMP280 for pressure. Just hook |
||||
them up to the Arduino’s I2C bus, load the available libraries and Bob’s |
||||
your uncle.</p> |
||||
<p>To mount these sensors on the same mast as the SparkFun weather meter |
||||
I use the RS1 passive radiation shield from Garni.</p> |
||||
<figure> |
||||
<img src="./images/garni_rs1.jpg" |
||||
title="Garni RS1 Passive Radiation Shield" |
||||
alt="Garni RS1 Passive Radiation Shield" /> |
||||
<figcaption aria-hidden="true">Garni RS1 Passive Radiation |
||||
Shield</figcaption> |
||||
</figure> |
||||
<h3 id="sensors">Sensors</h3> |
||||
<ul> |
||||
<li>SparkFun SEN-15901 Weather Station</li> |
||||
<li>Silicon Labs Si7021</li> |
||||
<li>Bosch BMP280</li> |
||||
</ul> |
||||
<h1 id="what-to-use-for-communication-with-the-outside-world">What to |
||||
use for communication with the outside world?</h1> |
||||
<p>Most consumer grade weather stations (and almost all other consumer |
||||
grade goods for that matter) use proprietary interfaces and protocols. |
||||
Probably to annoy the more technical skilled customer as you are not |
||||
able to interface these devices with other brands or self build systems. |
||||
I really hate that practice, so I won’t do that. Instead I will |
||||
implement a ModBus RTU interface. Dating back to 1979, this is the |
||||
industrial standard for communication between devices. And if the |
||||
professionals all use it, why not use it for this weather station?</p> |
||||
<h2 id="modbus">ModBus</h2> |
||||
<p>ModBus is a client/server data communications protocol in the |
||||
application layer of the OSI model. ModBus can work over several |
||||
different physical interfaces. For this application I will use an RS-485 |
||||
interface. This interface is easy to implement and cables can be very |
||||
long, making it easy to locate the weather station. ModBus is a |
||||
lightweight protocol which can comfortably fit inside an under-powered |
||||
micro-controller like an Atmel ATmega328P. A simple RS-485 to USB dongle |
||||
connected to a PC is all you need to read the values from the weather |
||||
station.</p> |
||||
<h1 id="what-else">What else?</h1> |
||||
<p>Not much to be honest. Almost everything can be done in software. Of |
||||
course we need a power supply. And preferably a reverse polarity |
||||
protection. An input voltage of 12 Volt is convenient. 12 Volt power |
||||
bricks can be found in every charity shop and you can also use a 12 Volt |
||||
lead acid or lithium battery to power the weather station.</p> |
||||
<h1 id="theory-of-operation---hardware">Theory of operation - |
||||
Hardware</h1> |
||||
<figure> |
||||
<img src="./images/block_diagram.svg" |
||||
title="Block diagram of weather station" |
||||
alt="Block diagram of weather station" /> |
||||
<figcaption aria-hidden="true">Block diagram of weather |
||||
station</figcaption> |
||||
</figure> |
||||
<h2 id="wind-speed">Wind speed</h2> |
||||
<p>Measuring the wind speed is done by a cup anemometer. It consisted of |
||||
three or four hemispherical cups on horizontal arms mounted on a |
||||
vertical shaft. The air flow past the cups in any horizontal direction |
||||
turned the shaft at a rate roughly proportional to the wind’s speed. |
||||
Every rotation, a magnet passes alongside a reed switch. The rate at |
||||
which the reed switch opens en closes is a measure of the wind |
||||
speed.</p> |
||||
<figure> |
||||
<img src="./images/sparkfun_cup_anemometer.jpg" title="Cup anemometer" |
||||
alt="Cup anemometer" /> |
||||
<figcaption aria-hidden="true">Cup anemometer</figcaption> |
||||
</figure> |
||||
<p>By connecting one side of the reed switch to ground and the other via |
||||
a pull-up resistor to the supply voltage the mechanical switching action |
||||
is translated to an electrical pulse signal. This pulse can be read by a |
||||
micro-controller.</p> |
||||
<figure> |
||||
<img src="./images/diagram_cup_anemometer.svg" |
||||
title="Cup anemometer: theory of operation" |
||||
alt="Cup anemometer: theory of operation" /> |
||||
<figcaption aria-hidden="true">Cup anemometer: theory of |
||||
operation</figcaption> |
||||
</figure> |
||||
<h2 id="wind-direction">Wind direction</h2> |
||||
<p>Measuring the wind direction is done by a wind vane. It consists of a |
||||
vertical blade mounted on a vertical shaft. Because the blade can turn, |
||||
it will always find the position of the least air resistance. The shape |
||||
of the blade is chosen so that it will always points directly to the |
||||
wind. A magnet mounted on the shaft rotates past several reed switches. |
||||
The switch that is closest to the magnet will close. If the magnet is |
||||
precisely between two reed switches both switches will close increasing |
||||
the resolution of the wind vane.</p> |
||||
<figure> |
||||
<img src="./images/sparkfun_wind_vane.jpg" title="Wind vane" |
||||
alt="Wind vane" /> |
||||
<figcaption aria-hidden="true">Wind vane</figcaption> |
||||
</figure> |
||||
<p>Each reed switch is connected to a resistor and every resister has a |
||||
different value. The total resistance of the network will change |
||||
according to the wind direction. By connecting one side of the network |
||||
to ground and the other side via a resistor to VCC, a resisive divider |
||||
in made. This resistive divider converts the variable resistance to an |
||||
analog voltage which can be sampled by the A/D converter of a |
||||
micro-controller.</p> |
||||
<figure> |
||||
<img src="./images/diagram_wind_vane.svg" |
||||
title="Wind vane: theory of operation" |
||||
alt="Wind vane: theory of operation" /> |
||||
<figcaption aria-hidden="true">Wind vane: theory of |
||||
operation</figcaption> |
||||
</figure> |
||||
<table> |
||||
<thead> |
||||
<tr class="header"> |
||||
<th>Direction</th> |
||||
<th>Resistance</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr class="odd"> |
||||
<td>0°</td> |
||||
<td>33kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>22.5°</td> |
||||
<td>6.57kΩ</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>45°</td> |
||||
<td>8.2kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>67.5°</td> |
||||
<td>891Ω</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>90°</td> |
||||
<td>1kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>112.5°</td> |
||||
<td>688Ω</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>135°</td> |
||||
<td>2.2kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>157.5°</td> |
||||
<td>1.41kΩ</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>180°</td> |
||||
<td>3.9kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>202.5°</td> |
||||
<td>3.14kΩ</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>225°</td> |
||||
<td>16kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>247.5°</td> |
||||
<td>14.12kΩ</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>270°</td> |
||||
<td>120kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>292.5°</td> |
||||
<td>42.12kΩ</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>315°</td> |
||||
<td>64.9kΩ</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>337.5°</td> |
||||
<td>21.88kΩ</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
<h2 id="rain-fall">Rain fall</h2> |
||||
<p>Measuring the amount of rain fall is done by a self-emptying tipping |
||||
bucket. Rainwater is collected and funneled to a tipping bucket. The |
||||
bucket tips over when a certain amount of water is collected. The bucket |
||||
drains and a second bucket is automatically placed under the funnel. |
||||
When a certain amount of water is collected in this second bucket it |
||||
will tip over and the first bucket is raised again.</p> |
||||
<figure> |
||||
<img src="./images/sparkfun_rain_meter_inside.jpg" title="Rain meter" |
||||
alt="Rain meter" /> |
||||
<figcaption aria-hidden="true">Rain meter</figcaption> |
||||
</figure> |
||||
<p>Every time the bucket tips over a magnet passes by a reed switch, |
||||
which closes and opens again. As with the cup anemometer this mechanical |
||||
movement can be translated to an electrical pulse by connecting one side |
||||
of the switch to ground and the other side via a pull-up resistor to |
||||
VCC. This pulse can than be read by a micro-controller.</p> |
||||
<p><sup>NOTE</sup> The rain meter is very sensitive: even a small amount |
||||
of movement and the bucket tips over. Mounting the rain meter in the |
||||
mast together with the wind meters can cause false triggers from the |
||||
rocking motion of the mast.</p> |
||||
<figure> |
||||
<img src="./images/diagram_rain_meter.svg" |
||||
title="Rain meter: theory of operation" |
||||
alt="Rain meter: theory of operation" /> |
||||
<figcaption aria-hidden="true">Rain meter: theory of |
||||
operation</figcaption> |
||||
</figure> |
||||
<h2 id="humidity">Humidity</h2> |
||||
<p>Measuring the relative humidity is done by an electronic sensor based |
||||
on capacitive sensing using polymeric dielectrics. The humidity sensor |
||||
is a small capacitor consisting of a hygroscopic dielectric material |
||||
placed between a pair of electrodes. Absorption of moisture by the |
||||
sensor results in an increase in sensor capacitance. The opposite is |
||||
also true: when the moisture disappears, sensor capacitane decreases. |
||||
There is a direct relationship between relative humidity, the amount of |
||||
moisture present in the sensor, and the sensor capacitance. The relative |
||||
humidity is defined as the ratio of the amount of water vapor in the air |
||||
at a specific temperature to the maximum amount that the air could hold |
||||
at that temperature, expressed as a percentage. As the humidity sensor |
||||
has a build in temperature sensor, it can calculate the relative |
||||
humidity.</p> |
||||
<figure> |
||||
<img src="./images/capacitive_humidity_sensor.png" |
||||
title="Humidity sensor" alt="Humidity sensor" /> |
||||
<figcaption aria-hidden="true">Humidity sensor</figcaption> |
||||
</figure> |
||||
<p>The Si7021 humidity sensor has an I²C bus for communication with a |
||||
micro-controller.</p> |
||||
<figure> |
||||
<img src="./images/Si7021_block_diagram.png" |
||||
title="Humidity sensor: block diagram" |
||||
alt="Humidity sensor: block diagram" /> |
||||
<figcaption aria-hidden="true">Humidity sensor: block |
||||
diagram</figcaption> |
||||
</figure> |
||||
<h2 id="temperature">Temperature</h2> |
||||
<p>Measuring the temperature is done by the build in temperature sensor |
||||
of the humidity sensor. This sensor is used by the humidity sensor to |
||||
calculate the relative humidity. But as this sensor is very accurate it |
||||
can be used for ambient temperature measurments.</p> |
||||
<h2 id="atmospheric-pressure">Atmospheric pressure</h2> |
||||
<p>Measuring the atmospheric pressure is done by an electronic sensor |
||||
based on a piezo-resistive pressure sensing element. The piezo-resistive |
||||
effect is a change in the electrical resistivity of a semiconductor or |
||||
metal when mechanical strain is applied. In this case the strain comes |
||||
from the atmospheric pressure. The sensor measures the resistance which |
||||
is proportional to the atmospheric pressure.</p> |
||||
<figure> |
||||
<img src="./images/piezo_resistive_pressure_sensor.png" |
||||
title="Piezo-resistive pressure sensor" |
||||
alt="Piezo-resistive pressure sensor" /> |
||||
<figcaption aria-hidden="true">Piezo-resistive pressure |
||||
sensor</figcaption> |
||||
</figure> |
||||
<p>The BMP280 pressure sensor has an on board temperature sensor which |
||||
can also be used to measure the ambient temperature. As this sensor is |
||||
less accurate compared to the sensor of the Si7021 humidity sensor, the |
||||
sensor is only used as a backup sensor. The BMP280 has an I²C bus for |
||||
communication with a micro-controller.</p> |
||||
<figure> |
||||
<img src="./images/BMP280_block_diagram.png" |
||||
title="Pressure sensor: block diagram" |
||||
alt="Pressure sensor: block diagram" /> |
||||
<figcaption aria-hidden="true">Pressure sensor: block |
||||
diagram</figcaption> |
||||
</figure> |
||||
<h2 id="illumination">Illumination</h2> |
||||
<p>This sensor is still under development.</p> |
||||
<h2 id="modbus-interface">ModBus interface</h2> |
||||
<p>The RS-485 interface is build with a MAX485E driver chip from Maxim |
||||
Integrated. Nothing much to say as the implementation is pretty much |
||||
following the typical application from the datasheet.</p> |
||||
<figure> |
||||
<img src="./images/rs-485.svg" title="RS-485 interface" |
||||
alt="RS-485 interface" /> |
||||
<figcaption aria-hidden="true">RS-485 interface</figcaption> |
||||
</figure> |
||||
<p>If the device is the first or last device on the RS-485 bus, a 120 |
||||
Ohm termination resistor can be enabled by placing a jumper on header |
||||
J7.</p> |
||||
<p>The Arduino micro-controller can be programmed via an in circuit |
||||
programmer, which shares the serial port with the MAX485E. Resistor R5 |
||||
isolates the output of the MAX485 from the signal of the programmer.</p> |
||||
<h2 id="i²c-bus">I²C bus</h2> |
||||
<p>The I²C bus is integrated in the micro-controller. But because the |
||||
micro-controller uses a power supply of 5 Volt and the I²C sensors use |
||||
3.3 Volt a bidirectional level shifter is needed. This way the sensors |
||||
can be used on the 5V I²C bus without the risk of damaging the |
||||
sensors.</p> |
||||
<figure> |
||||
<img src="./images/i2c_bus.svg" title="I²C bus level shifter" |
||||
alt="I²C bus level shifter" /> |
||||
<figcaption aria-hidden="true">I²C bus level shifter</figcaption> |
||||
</figure> |
||||
<p>Let’s assume the I²C signal lines on either end of the MOSFETs are |
||||
either outputting a logic high or is configured as an input. Effectively |
||||
this means there is nothing pulling the signal levels down.</p> |
||||
<p>The voltage between the gate and source of both MOSFETs is at 0V |
||||
(both are at 3.3V) so the MOSFET is switched off. Therefore both sides |
||||
of the MOSFETs are logic high.</p> |
||||
<p>When either of the 3.3 Volt signal lines outputs a logic low the |
||||
corresponding drain is pulled to ground. Now the voltage between the |
||||
gate and the source is 3.3V and the MOSFET turns on causing the 5 Volt |
||||
side to go low as well.</p> |
||||
<p>When either of the 5 Volt signal lines outputs a logic low the body |
||||
diode of the corresponding MOSFET start conducting, causing the source |
||||
voltage to drop below the gate voltage. The MOSFET switches on and the |
||||
3.3 volt side goes low.</p> |
||||
<h2 id="power-supply">Power supply</h2> |
||||
<p>Typical, a 12 Volt power supply is used to power the device, but it |
||||
can be powered from a wide range of voltages, from 6.5 to 36 Volt. A |
||||
switching regulator (U3) supplies the 5 Volt power rail and a linear low |
||||
drop regulator (U4) supplies the 3.3 volt power rail.</p> |
||||
<figure> |
||||
<img src="./images/power_supply.svg" title="Power supply" |
||||
alt="Power supply" /> |
||||
<figcaption aria-hidden="true">Power supply</figcaption> |
||||
</figure> |
||||
<h3 id="input-protection">Input protection</h3> |
||||
<p>C1 and C5 short out high frequency signals, protecting the input from |
||||
ESD. Bidirectional transient-voltage-suppression diodes D1 end D2 clamp |
||||
transient voltages, again protecting the input from ESD.</p> |
||||
<p>And than Q3 and its surrounding components: this is the reverse |
||||
polarity protection. Usually, a series diode is used, but due to the |
||||
voltage drop across such a diode it dissipates energy which is wasteful. |
||||
The circuit with Q3 on the other hand has a very low voltage drop |
||||
resulting in an almost zero loss solution.</p> |
||||
<p>If VCC is applied in the correct polarity, the source will |
||||
immediately rise to the about VCC because of the body diode |
||||
conducting.</p> |
||||
<p>The gate will charge towards -VCC with respect to the source through |
||||
R1. When the gate reaches the threshold voltage the MOSFET channel will |
||||
begin to conduct, and by the time the gate-source voltage reaches a few |
||||
volts the MOSFET channel will be conducting almost all the current, the |
||||
output voltage will be close to VCC. It continues to charge until it |
||||
reaches about -10V at which point the zener diode begins to shunt |
||||
significant current away from the gate.</p> |
||||
<p>In steady state with VCC on the drain the gate sits at -10V with |
||||
respect to the source, and the MOSFET happily conducts in the reverse |
||||
direction.</p> |
||||
<p>When VCC is applied in the reverse polarity, the body diode of the |
||||
MOSFET cannot conduct. Only a small leakage current can flow from the |
||||
source to the drain via resistor R1 and zener diode D3, which now acts |
||||
as a normal diode. The gate and the source are now at almost the same |
||||
potential and the MOSFET cannot conduct, protecting the device from |
||||
reverse polarity.</p> |
||||
<figure> |
||||
<img src="./images/reverse_polarity_protection.svg" |
||||
title="Reverse polarity protection" alt="Reverse polarity protection" /> |
||||
<figcaption aria-hidden="true">Reverse polarity protection</figcaption> |
||||
</figure> |
||||
<h2 id="microcontroller">Microcontroller</h2> |
||||
<p>The heart of the circuit is an Arduino Pro Mini, which is basically |
||||
an Atmel ATmega328P with a special Arduino bootloader, making it an easy |
||||
platform for developing software.</p> |
||||
<figure> |
||||
<img src="./images/micro-controller.svg" title="Microcontroller" |
||||
alt="Microcontroller" /> |
||||
<figcaption aria-hidden="true">Microcontroller</figcaption> |
||||
</figure> |
||||
<p>Both the signals from the rain meter and the cup anemometer are |
||||
connected to interrupt pins of the micro-controller. The signal from the |
||||
rain meter is lightly filtered by C8.</p> |
||||
<p>The ModBus address can be set by DIP switch J9.</p> |
||||
<h1 id="theory-of-operation---software">Theory of operation - |
||||
Software</h1> |
||||
<h2 id="wind-speed-1">Wind speed</h2> |
||||
<p>The pulse from the cup anemometer is connected to an interrupt input |
||||
of the micro-controller. Every time its logic level changes an interrupt |
||||
routine is called. This routine increments a counter and checks how many |
||||
time has passed since the previous interrupt call. If the previous call |
||||
was more than 2 seconds ago, the wind speed is (almost) zero. If the |
||||
previous call was just over a second ago the interrupt counter now holds |
||||
the amount of pulses in one second. This value is stored and from that |
||||
value the wind speed can be calculated. If the previous call was under a |
||||
second ago the measurement is still in progress and no further action is |
||||
taken.</p> |
||||
<figure> |
||||
<img src="./images/wind_speed_diagram.svg" title="Wind speed interrupt" |
||||
alt="Wind speed interrupt" /> |
||||
<figcaption aria-hidden="true">Wind speed interrupt</figcaption> |
||||
</figure> |
||||
<h2 id="wind-direction-1">Wind direction</h2> |
||||
<p>The analog signal from the wind vane is fed into the analog to |
||||
digital converter of the micro-controller. The software samples this |
||||
signal and determines which value from a lookup table is closest to the |
||||
value from the ADC. The lookup table now gives the wind direction in |
||||
degrees.</p> |
||||
<figure> |
||||
<img src="./images/wind_direction_diagram.svg" |
||||
title="Getting the wind direction" alt="Getting the wind direction" /> |
||||
<figcaption aria-hidden="true">Getting the wind direction</figcaption> |
||||
</figure> |
||||
<p>As the tolerances between micro-controllers can be high, the wind |
||||
vane has to be calibrated in order to get a correct lookup table.</p> |
||||
<h2 id="rain-fall-1">Rain fall</h2> |
||||
<p>The pulse from the rain meter is connected to an interrupt input of |
||||
the micro-controller. Every time a rising edge is detected an interrupt |
||||
routine is called. This routine debounces the signal and increments the |
||||
rain counter. This counter can be used to calculate the rain fall.</p> |
||||
<h2 id="humidity-1">Humidity</h2> |
||||
<p>Via the I²C bus, the humidity value of the sensor is read. As the |
||||
sensor can become saturated with moisture it can get stuck at 100%. This |
||||
happens in particular with fog or other high humidity and condensing |
||||
weather types. The sensor has a build in heater to drive of moisture and |
||||
thus preventing this problem. Because the temperature of the sensor |
||||
rises when the heater is turned on, accurate ambient temperature |
||||
readings are no longer possible. But with a smart algorithm it is |
||||
possible to get the benefits of the build in heater while still being |
||||
able to use the sensor as an ambient thermometer.</p> |
||||
<p>When the humidity rises above 95% for more than an hour the current |
||||
temperature is stored and the heater is switched on for 10 minutes. Than |
||||
the heater is switched off again. If after 10 minutes the humidity is |
||||
still above 95% the heater is turned on again for another 10 minutes. |
||||
But not before the temperature is measured and stored, as the sensor is |
||||
now cooled off to ambient temperature. If the humidity is below 95% the |
||||
sensor is free from moisture and the process is not repeated for another |
||||
hour.</p> |
||||
<p>Flow chart under development.</p> |
||||
<h2 id="temperature-1">Temperature</h2> |
||||
<p>The temperature is read from the humidity sensor as this sensor gives |
||||
the most accurate temperature readings. When the heater is on (see |
||||
section humidity above) the temperature readings are temporary stopped |
||||
and only updated every 20 minutes. As a backup, the slightly less |
||||
accurate temperature readings from the pressure sensor can be used.</p> |
||||
<h2 id="atmospheric-pressure-1">Atmospheric pressure</h2> |
||||
<p>Via the I²C bus, the atmospheric pressure value of the sensor is |
||||
read. There is nothing further to say about this sensor: it is rather |
||||
boring.</p> |
||||
<h2 id="illumination-1">Illumination</h2> |
||||
<p>This sensor is still under development.</p> |
||||
<h2 id="modbus-interface-1">ModBus interface</h2> |
||||
<p>The weather station uses ModBus RTU over a simplex RS-485 line. For |
||||
now, the ModBus address is hard coded as 14 in the software. The values |
||||
are available in the input registers and can be read via function code |
||||
04.</p> |
||||
<p>Below an example of how to read the wind direction in Python using |
||||
the minimalmodbus library.</p> |
||||
<pre><code>#!/usr/bin/env python3 |
||||
import minimalmodbus |
||||
|
||||
# port name, slave address (in decimal) |
||||
instrument = minimalmodbus.Instrument('/dev/ttyUSB1', 14) |
||||
|
||||
# Register number, number of decimals, function code |
||||
wind_direction = instrument.read_register(1, 0, 4) |
||||
print(wind_direction)</code></pre> |
||||
<h3 id="input-registers">Input registers</h3> |
||||
<p>The measurements and order of the measurements are the same as for |
||||
APRS weather reports. But of course we use SI units.</p> |
||||
<table> |
||||
<colgroup> |
||||
<col style="width: 13%" /> |
||||
<col style="width: 53%" /> |
||||
<col style="width: 33%" /> |
||||
</colgroup> |
||||
<thead> |
||||
<tr class="header"> |
||||
<th>Address</th> |
||||
<th>Description</th> |
||||
<th>Units</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr class="odd"> |
||||
<td>30000</td> |
||||
<td>Device ID (0x5758)</td> |
||||
<td>NO UNIT</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>30001</td> |
||||
<td>Wind direction</td> |
||||
<td>degrees</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>30002</td> |
||||
<td>Wind speed (average of 10 minutes)</td> |
||||
<td>m/s * 100</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>30003</td> |
||||
<td>Wind gust (peak of last 10 minutes)</td> |
||||
<td>m/s * 100</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>30004</td> |
||||
<td>Temperature (two’s complement)</td> |
||||
<td>degrees Celcius * 100</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>30005</td> |
||||
<td>Rain last hour</td> |
||||
<td>l/m2 * 100</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>30006</td> |
||||
<td>Rain last 24 hours</td> |
||||
<td>l/m2 * 100</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>30007</td> |
||||
<td>Rain since midnight</td> |
||||
<td>NOT IMPLEMENTED</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>30008</td> |
||||
<td>Humidity</td> |
||||
<td>percent * 100</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>30009</td> |
||||
<td>Barometric pressure</td> |
||||
<td>hPa * 10</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>30010</td> |
||||
<td>Luminosity</td> |
||||
<td>W/m2</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>30011</td> |
||||
<td>Snow fall</td> |
||||
<td>NOT IMPLEMENTED</td> |
||||
</tr> |
||||
<tr class="odd"> |
||||
<td>30012</td> |
||||
<td>Raw rain counter</td> |
||||
<td>NOT IMPLEMENTED</td> |
||||
</tr> |
||||
<tr class="even"> |
||||
<td>30013</td> |
||||
<td>Temperature (two’s complement)</td> |
||||
<td>degrees Celcius * 100</td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
<p><sup>NOTE</sup> Register 30013 holds the backup temperature reading |
||||
from the pressure sensor.</p> |
||||
<p>The ModBus registers are 16 bit wide. For better precision, some |
||||
units are scaled by a factor of 10 or 100. This way, values with up to |
||||
two decimal points can be stored as 16 bit integer values. Just divide |
||||
by 10 or 100 to get the floating point values.</p> |
||||
<h1 id="schematic">Schematic</h1> |
||||
<p><a href="./images/weather_station_schematic.pdf"><img |
||||
src="./images/weather_station_schematic.svg" alt="Schematic" /></a></p> |
||||
<!--- |
||||
# Bill of materials |
||||
|
||||
# Component placement |
||||
|
||||
# Cables and pinouts |
||||
--> |
||||
<h1 id="software-dependencies">Software dependencies</h1> |
||||
<ul> |
||||
<li>Arduino IDE</li> |
||||
</ul> |
||||
<h2 id="arduino-libraries">Arduino libraries</h2> |
||||
<ul> |
||||
<li>https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library</li> |
||||
<li>https://github.com/orgua/iLib</li> |
||||
<li>https://github.com/epsilonrt/modbus-arduino</li> |
||||
<li>https://github.com/epsilonrt/modbus-serial</li> |
||||
</ul> |
||||
<p>Libraries are included with the source code of this project</p> |
||||
<h1 id="license">License</h1> |
||||
<p>Copyright (C) 2023, 2024 M.T. Konstapel</p> |
||||
<p>The software is published as open-source software (GPL). The hardware |
||||
is published as open-source hardware (OSH).</p> |
||||
<h2 id="software">Software</h2> |
||||
<p>This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by the |
||||
Free Software Foundation, either version 3 of the License, or (at your |
||||
option) any later version.</p> |
||||
<h2 id="hardware-and-documentation">Hardware and documentation</h2> |
||||
<p>This work is licensed under a Creative Commons Attribution-ShareAlike |
||||
4.0 International License.</p> |
||||
<hr> |
||||
</article> |
||||
</main> |
||||
<footer> |
||||
<p>© |
||||
2024-01-15 |
||||
M.T. Konstapel |
||||
<a href="https://meezenest.nl/mees/">https://meezenest.nl/mees/</a> |
||||
</p><p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>. |
||||
</p> |
||||
</footer> |
||||
</body> |
||||
</html> |
@ -0,0 +1,334 @@ |
||||
--- |
||||
title: Weather station |
||||
subtitle: with ModBus RTU interface |
||||
author: M.T. Konstapel |
||||
date: 2024-01-15 |
||||
website: https://meezenest.nl/mees/ |
||||
logo: ./images/mees_logo.svg |
||||
numbersections: true |
||||
abstract: > |
||||
A weather station build around a SparkFun Weather Meter Kit (SEN-15901). The temperature, humidity and pressure are measured with I2C sensors housed in an RS1 Passive Radiation Shield from Garni. The data can be read via an RS485 ModBus RTU interface. The main processor is an Arduino Pro Mini (ATmega328P 5V@16MHz) |
||||
--- |
||||
|
||||
# Why do you need a weather station? |
||||
|
||||
Well, you don't...because if you want to know the weather, you look on your phone. So why bother than? Because since the beginning of time, people are obsessed with the weather. When I was a child, my grandmother was measuring the temperature and rainfall on a daily basis. My grandfather had an allotment, so he also was very interested in the weather. The first thing my father read in the newspaper was the weather report and the last thing he watched in the television was... the weather report. And every hour he listed to the weather report on the radio. If he talked to someone he always started the conversation by talking about the weather. And when I open a new browser window, it automatically opens the weather page. |
||||
|
||||
So the weather is fascinating and taking your own measurements is a lot of fun. |
||||
|
||||
# What should a weather station measure? |
||||
|
||||
As my grandmother already measured temperature and rainfall, these ones are mandatory. And for the rest I looked at the website of the Dutch meteorological institute. They measure wind direction, average wind speed of the last 10 minutes, maximum wind gust of the last 10 minutes, rainfall of the last hour as well as the last 24 hours, temperature, humidity and atmospheric pressure. |
||||
|
||||
### Measurements |
||||
|
||||
- Wind direction |
||||
- Wind speed (average of last 10 minutes) |
||||
- Wind gust (last 10 minutes) |
||||
- Rain fall (last hour) |
||||
- Rain fall (last 24 hours) |
||||
- Temperature |
||||
- Humidity |
||||
- Atmospheric pressure |
||||
|
||||
# What sensors do we need? |
||||
|
||||
## Wind and rain |
||||
|
||||
Measuring wind and rain is difficult. Well, not if you want to do it by hand: place a beaker on the ground and wait a day. Than measure the amount of water in it. Empty the beaker and start again. And for the wind, you can stick a pole in the ground and attach a ribbon to it. The direction of the wind can than be made visible. And even the wind speed can be determent by measuring the angle between the ribbon and the ground. |
||||
|
||||
But how to do this automatically? Of course you can buy a fancy commercial weather station. These are surprisingly cheap these days. But that's not a challenge. Besides, than you buy into a proprietary ecosystem. And it probably only works when connected to the cloud. No thanks! |
||||
|
||||
Building from scratch is an option, but I am an electronic engineer, not a mechanical one. I can imagine that won't be a success. Besides going the professional route, which is ridiculously expensive, there is really only one option left: the SparkFun SEN-15901 Weather Meter. |
||||
|
||||
![SparkFun Weather Meter](./images/SparkFun-Weather_Meter.jpg "SparkFun Weather Meter") |
||||
|
||||
But this contraption does not come with any signal conditioning. We have to make some kind of interface. Luckily, Sparkfun provides an Arduino library, so we only have to connect the SEN-15901 to an Arduino and run the code. |
||||
|
||||
## Temperature, humidity and air pressure |
||||
|
||||
These three are easy: there are a lot of I2C chips capable of measuring these parameters. I choose the Silicon Labs Si7021 for humidity and temperature and the Bosch BMP280 for pressure. Just hook them up to the Arduino's I2C bus, load the available libraries and Bob's your uncle. |
||||
|
||||
To mount these sensors on the same mast as the SparkFun weather meter I use the RS1 passive radiation shield from Garni. |
||||
|
||||
![Garni RS1 Passive Radiation Shield](./images/garni_rs1.jpg "Garni RS1 Passive Radiation Shield") |
||||
|
||||
### Sensors |
||||
|
||||
- SparkFun SEN-15901 Weather Station |
||||
- Silicon Labs Si7021 |
||||
- Bosch BMP280 |
||||
|
||||
# What to use for communication with the outside world? |
||||
|
||||
Most consumer grade weather stations (and almost all other consumer grade goods for that matter) use proprietary interfaces and protocols. Probably to annoy the more technical skilled customer as you are not able to interface these devices with other brands or self build systems. I really hate that practice, so I won't do that. Instead I will implement a ModBus RTU interface. Dating back to 1979, this is the industrial standard for communication between devices. And if the professionals all use it, why not use it for this weather station? |
||||
|
||||
## ModBus |
||||
|
||||
ModBus is a client/server data communications protocol in the application layer of the OSI model. ModBus can work over several different physical interfaces. For this application I will use an RS-485 interface. This interface is easy to implement and cables can be very long, making it easy to locate the weather station. ModBus is a lightweight protocol which can comfortably fit inside an under-powered micro-controller like an Atmel ATmega328P. A simple RS-485 to USB dongle connected to a PC is all you need to read the values from the weather station. |
||||
|
||||
# What else? |
||||
|
||||
Not much to be honest. Almost everything can be done in software. Of course we need a power supply. And preferably a reverse polarity protection. An input voltage of 12 Volt is convenient. 12 Volt power bricks can be found in every charity shop and you can also use a 12 Volt lead acid or lithium battery to power the weather station. |
||||
|
||||
# Theory of operation - Hardware |
||||
|
||||
![Block diagram of weather station](./images/block_diagram.svg "Block diagram of weather station") |
||||
|
||||
## Wind speed |
||||
|
||||
Measuring the wind speed is done by a cup anemometer. It consisted of three or four hemispherical cups on horizontal arms mounted on a vertical shaft. The air flow past the cups in any horizontal direction turned the shaft at a rate roughly proportional to the wind's speed. Every rotation, a magnet passes alongside a reed switch. The rate at which the reed switch opens en closes is a measure of the wind speed. |
||||
|
||||
![Cup anemometer](./images/sparkfun_cup_anemometer.jpg "Cup anemometer") |
||||
|
||||
By connecting one side of the reed switch to ground and the other via a pull-up resistor to the supply voltage the mechanical switching action is translated to an electrical pulse signal. This pulse can be read by a micro-controller. |
||||
|
||||
![Cup anemometer: theory of operation](./images/diagram_cup_anemometer.svg "Cup anemometer: theory of operation") |
||||
|
||||
## Wind direction |
||||
|
||||
Measuring the wind direction is done by a wind vane. It consists of a vertical blade mounted on a vertical shaft. Because the blade can turn, it will always find the position of the least air resistance. The shape of the blade is chosen so that it will always points directly to the wind. A magnet mounted on the shaft rotates past several reed switches. The switch that is closest to the magnet will close. If the magnet is precisely between two reed switches both switches will close increasing the resolution of the wind vane. |
||||
|
||||
![Wind vane](./images/sparkfun_wind_vane.jpg "Wind vane") |
||||
|
||||
Each reed switch is connected to a resistor and every resister has a different value. The total resistance of the network will change according to the wind direction. By connecting one side of the network to ground and the other side via a resistor to VCC, a resisive divider in made. This resistive divider converts the variable resistance to an analog voltage which can be sampled by the A/D converter of a micro-controller. |
||||
|
||||
![Wind vane: theory of operation](./images/diagram_wind_vane.svg "Wind vane: theory of operation") |
||||
|
||||
| Direction | Resistance | |
||||
|-----------|------------| |
||||
| 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Ω | |
||||
|
||||
## Rain fall |
||||
|
||||
Measuring the amount of rain fall is done by a self-emptying tipping bucket. Rainwater is collected and funneled to a tipping bucket. The bucket tips over when a certain amount of water is collected. The bucket drains and a second bucket is automatically placed under the funnel. When a certain amount of water is collected in this second bucket it will tip over and the first bucket is raised again. |
||||
|
||||
![Rain meter](./images/sparkfun_rain_meter_inside.jpg "Rain meter") |
||||
|
||||
Every time the bucket tips over a magnet passes by a reed switch, which closes and opens again. As with the cup anemometer this mechanical movement can be translated to an electrical pulse by connecting one side of the switch to ground and the other side via a pull-up resistor to VCC. This pulse can than be read by a micro-controller. |
||||
|
||||
^NOTE^ The rain meter is very sensitive: even a small amount of movement and the bucket tips over. Mounting the rain meter in the mast together with the wind meters can cause false triggers from the rocking motion of the mast. |
||||
|
||||
![Rain meter: theory of operation](./images/diagram_rain_meter.svg "Rain meter: theory of operation") |
||||
|
||||
## Humidity |
||||
|
||||
Measuring the relative humidity is done by an electronic sensor based on capacitive sensing using polymeric dielectrics. The humidity sensor is a small capacitor consisting of a hygroscopic dielectric material placed between a pair of electrodes. Absorption of moisture by the sensor results in an increase in sensor capacitance. The opposite is also true: when the moisture disappears, sensor capacitane decreases. There is a direct relationship between relative humidity, the amount of moisture present in the sensor, and the sensor capacitance. The relative humidity is defined as the ratio of the amount of water vapor in the air at a specific temperature to the maximum amount that the air could hold at that temperature, expressed as a percentage. As the humidity sensor has a build in temperature sensor, it can calculate the relative humidity. |
||||
|
||||
![Humidity sensor](./images/capacitive_humidity_sensor.png "Humidity sensor") |
||||
|
||||
The Si7021 humidity sensor has an I²C bus for communication with a micro-controller. |
||||
|
||||
![Humidity sensor: block diagram](./images/Si7021_block_diagram.png "Humidity sensor: block diagram") |
||||
|
||||
## Temperature |
||||
|
||||
Measuring the temperature is done by the build in temperature sensor of the humidity sensor. This sensor is used by the humidity sensor to calculate the relative humidity. But as this sensor is very accurate it can be used for ambient temperature measurments. |
||||
|
||||
## Atmospheric pressure |
||||
|
||||
Measuring the atmospheric pressure is done by an electronic sensor based on a piezo-resistive pressure sensing element. The piezo-resistive effect is a change in the electrical resistivity of a semiconductor or metal when mechanical strain is applied. In this case the strain comes from the atmospheric pressure. The sensor measures the resistance which is proportional to the atmospheric pressure. |
||||
|
||||
![Piezo-resistive pressure sensor](./images/piezo_resistive_pressure_sensor.png "Piezo-resistive pressure sensor") |
||||
|
||||
The BMP280 pressure sensor has an on board temperature sensor which can also be used to measure the ambient temperature. As this sensor is less accurate compared to the sensor of the Si7021 humidity sensor, the sensor is only used as a backup sensor. The BMP280 has an I²C bus for communication with a micro-controller. |
||||
|
||||
![Pressure sensor: block diagram](./images/BMP280_block_diagram.png "Pressure sensor: block diagram") |
||||
|
||||
## Illumination |
||||
|
||||
This sensor is still under development. |
||||
|
||||
## ModBus interface |
||||
|
||||
The RS-485 interface is build with a MAX485E driver chip from Maxim Integrated. Nothing much to say as the implementation is pretty much following the typical application from the datasheet. |
||||
|
||||
![RS-485 interface](./images/rs-485.svg "RS-485 interface") |
||||
|
||||
If the device is the first or last device on the RS-485 bus, a 120 Ohm termination resistor can be enabled by placing a jumper on header J7. |
||||
|
||||
The Arduino micro-controller can be programmed via an in circuit programmer, which shares the serial port with the MAX485E. Resistor R5 isolates the output of the MAX485 from the signal of the programmer. |
||||
|
||||
## I²C bus |
||||
|
||||
The I²C bus is integrated in the micro-controller. But because the micro-controller uses a power supply of 5 Volt and the I²C sensors use 3.3 Volt a bidirectional level shifter is needed. This way the sensors can be used on the 5V I²C bus without the risk of damaging the sensors. |
||||
|
||||
![I²C bus level shifter](./images/i2c_bus.svg "I²C bus level shifter") |
||||
|
||||
Let's assume the I²C signal lines on either end of the MOSFETs are either outputting a logic high or is configured as an input. Effectively this means there is nothing pulling the signal levels down. |
||||
|
||||
The voltage between the gate and source of both MOSFETs is at 0V (both are at 3.3V) so the MOSFET is switched off. Therefore both sides of the MOSFETs are logic high. |
||||
|
||||
When either of the 3.3 Volt signal lines outputs a logic low the corresponding drain is pulled to ground. Now the voltage between the gate and the source is 3.3V and the MOSFET turns on causing the 5 Volt side to go low as well. |
||||
|
||||
When either of the 5 Volt signal lines outputs a logic low the body diode of the corresponding MOSFET start conducting, causing the source voltage to drop below the gate voltage. The MOSFET switches on and the 3.3 volt side goes low. |
||||
|
||||
## Power supply |
||||
|
||||
Typical, a 12 Volt power supply is used to power the device, but it can be powered from a wide range of voltages, from 6.5 to 36 Volt. A switching regulator (U3) supplies the 5 Volt power rail and a linear low drop regulator (U4) supplies the 3.3 volt power rail. |
||||
|
||||
![Power supply](./images/power_supply.svg "Power supply") |
||||
|
||||
### Input protection |
||||
|
||||
C1 and C5 short out high frequency signals, protecting the input from ESD. Bidirectional transient-voltage-suppression diodes D1 end D2 clamp transient voltages, again protecting the input from ESD. |
||||
|
||||
And than Q3 and its surrounding components: this is the reverse polarity protection. Usually, a series diode is used, but due to the voltage drop across such a diode it dissipates energy which is wasteful. The circuit with Q3 on the other hand has a very low voltage drop resulting in an almost zero loss solution. |
||||
|
||||
If VCC is applied in the correct polarity, the source will immediately rise to the about VCC because of the body diode conducting. |
||||
|
||||
The gate will charge towards -VCC with respect to the source through R1. When the gate reaches the threshold voltage the MOSFET channel will begin to conduct, and by the time the gate-source voltage reaches a few volts the MOSFET channel will be conducting almost all the current, the output voltage will be close to VCC. It continues to charge until it reaches about -10V at which point the zener diode begins to shunt significant current away from the gate. |
||||
|
||||
In steady state with VCC on the drain the gate sits at -10V with respect to the source, and the MOSFET happily conducts in the reverse direction. |
||||
|
||||
When VCC is applied in the reverse polarity, the body diode of the MOSFET cannot conduct. Only a small leakage current can flow from the source to the drain via resistor R1 and zener diode D3, which now acts as a normal diode. The gate and the source are now at almost the same potential and the MOSFET cannot conduct, protecting the device from reverse polarity. |
||||
|
||||
![Reverse polarity protection](./images/reverse_polarity_protection.svg "Reverse polarity protection") |
||||
|
||||
## Microcontroller |
||||
|
||||
The heart of the circuit is an Arduino Pro Mini, which is basically an Atmel ATmega328P with a special Arduino bootloader, making it an easy platform for developing software. |
||||
|
||||
![Microcontroller](./images/micro-controller.svg "Microcontroller") |
||||
|
||||
Both the signals from the rain meter and the cup anemometer are connected to interrupt pins of the micro-controller. The signal from the rain meter is lightly filtered by C8. |
||||
|
||||
The ModBus address can be set by DIP switch J9. |
||||
|
||||
# Theory of operation - Software |
||||
|
||||
## Wind speed |
||||
|
||||
The pulse from the cup anemometer is connected to an interrupt input of the micro-controller. Every time its logic level changes an interrupt routine is called. This routine increments a counter and checks how many time has passed since the previous interrupt call. If the previous call was more than 2 seconds ago, the wind speed is (almost) zero. If the previous call was just over a second ago the interrupt counter now holds the amount of pulses in one second. This value is stored and from that value the wind speed can be calculated. If the previous call was under a second ago the measurement is still in progress and no further action is taken. |
||||
|
||||
![Wind speed interrupt](./images/wind_speed_diagram.svg "Wind speed interrupt") |
||||
|
||||
## Wind direction |
||||
|
||||
The analog signal from the wind vane is fed into the analog to digital converter of the micro-controller. The software samples this signal and determines which value from a lookup table is closest to the value from the ADC. The lookup table now gives the wind direction in degrees. |
||||
|
||||
![Getting the wind direction](./images/wind_direction_diagram.svg "Getting the wind direction") |
||||
|
||||
As the tolerances between micro-controllers can be high, the wind vane has to be calibrated in order to get a correct lookup table. |
||||
|
||||
## Rain fall |
||||
|
||||
The pulse from the rain meter is connected to an interrupt input of the micro-controller. Every time a rising edge is detected an interrupt routine is called. This routine debounces the signal and increments the rain counter. This counter can be used to calculate the rain fall. |
||||
|
||||
## Humidity |
||||
|
||||
Via the I²C bus, the humidity value of the sensor is read. As the sensor can become saturated with moisture it can get stuck at 100%. This happens in particular with fog or other high humidity and condensing weather types. The sensor has a build in heater to drive of moisture and thus preventing this problem. Because the temperature of the sensor rises when the heater is turned on, accurate ambient temperature readings are no longer possible. But with a smart algorithm it is possible to get the benefits of the build in heater while still being able to use the sensor as an ambient thermometer. |
||||
|
||||
When the humidity rises above 95% for more than an hour the current temperature is stored and the heater is switched on for 10 minutes. Than the heater is switched off again. If after 10 minutes the humidity is still above 95% the heater is turned on again for another 10 minutes. But not before the temperature is measured and stored, as the sensor is now cooled off to ambient temperature. If the humidity is below 95% the sensor is free from moisture and the process is not repeated for another hour. |
||||
|
||||
Flow chart under development. |
||||
|
||||
## Temperature |
||||
|
||||
The temperature is read from the humidity sensor as this sensor gives the most accurate temperature readings. When the heater is on (see section humidity above) the temperature readings are temporary stopped and only updated every 20 minutes. As a backup, the slightly less accurate temperature readings from the pressure sensor can be used. |
||||
|
||||
## Atmospheric pressure |
||||
|
||||
Via the I²C bus, the atmospheric pressure value of the sensor is read. There is nothing further to say about this sensor: it is rather boring. |
||||
|
||||
## Illumination |
||||
|
||||
This sensor is still under development. |
||||
|
||||
## ModBus interface |
||||
|
||||
The weather station uses ModBus RTU over a simplex RS-485 line. For now, the ModBus address is hard coded as 14 in the software. The values are available in the input registers and can be read via function code 04. |
||||
|
||||
Below an example of how to read the wind direction in Python using the minimalmodbus library. |
||||
|
||||
#!/usr/bin/env python3 |
||||
import minimalmodbus |
||||
|
||||
# port name, slave address (in decimal) |
||||
instrument = minimalmodbus.Instrument('/dev/ttyUSB1', 14) |
||||
|
||||
# register number, number of decimals, function code |
||||
wind_direction = instrument.read_register(1, 0, 4) |
||||
print(wind_direction) |
||||
|
||||
### Input registers |
||||
|
||||
The measurements and order of the measurements are the same as for APRS weather reports. But of course we use SI units. |
||||
|
||||
| Address | Description | Units | |
||||
|---------|-------------------------------------|-----------------------| |
||||
| 30000 | Device ID (0x5758) | NO UNIT | |
||||
| 30001 | Wind direction | degrees | |
||||
| 30002 | Wind speed (average of 10 minutes) | m/s * 100 | |
||||
| 30003 | Wind gust (peak of last 10 minutes) | m/s * 100 | |
||||
| 30004 | Temperature (two's complement) | degrees Celcius * 100 | |
||||
| 30005 | Rain last hour | l/m2 * 100 | |
||||
| 30006 | Rain last 24 hours | l/m2 * 100 | |
||||
| 30007 | Rain since midnight | NOT IMPLEMENTED | |
||||
| 30008 | Humidity | percent * 100 | |
||||
| 30009 | Barometric pressure | hPa * 10 | |
||||
| 30010 | Luminosity | W/m2 | |
||||
| 30011 | Snow fall | NOT IMPLEMENTED | |
||||
| 30012 | Raw rain counter | NOT IMPLEMENTED | |
||||
| 30013 | Temperature (two's complement) | degrees Celcius * 100 | |
||||
|
||||
^NOTE^ Register 30013 holds the backup temperature reading from the pressure sensor. |
||||
|
||||
The ModBus registers are 16 bit wide. For better precision, some units are scaled by a factor of 10 or 100. This way, values with up to two decimal points can be stored as 16 bit integer values. Just divide by 10 or 100 to get the floating point values. |
||||
|
||||
# Schematic |
||||
|
||||
[![Schematic](./images/weather_station_schematic.svg)](./images/weather_station_schematic.pdf) |
||||
|
||||
<!--- |
||||
# Bill of materials |
||||
|
||||
# Component placement |
||||
|
||||
# Cables and pinouts |
||||
--> |
||||
|
||||
# Software dependencies |
||||
|
||||
- Arduino IDE |
||||
|
||||
## Arduino libraries |
||||
|
||||
- https://github.com/sparkfun/SparkFun_Weather_Meter_Kit_Arduino_Library |
||||
- https://github.com/orgua/iLib |
||||
- https://github.com/epsilonrt/modbus-arduino |
||||
- https://github.com/epsilonrt/modbus-serial |
||||
|
||||
Libraries are included with the source code of this project |
||||
|
||||
# License |
||||
|
||||
Copyright (C) 2023, 2024 M.T. Konstapel |
||||
|
||||
The software is published as open-source software (GPL). The hardware is published as open-source hardware (OSH). |
||||
|
||||
## Software |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
## Hardware and documentation |
||||
|
||||
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. |
After Width: | Height: | Size: 586 KiB |