Compare commits

..

23 Commits

Author SHA1 Message Date
King Kévin 18f541adbb README: document HBMBSG02 2020-12-10 20:08:52 +01:00
King Kévin b9df8e7e30 application: add segment selector 2020-12-10 18:19:51 +01:00
King Kévin b531e4cde3 application: add tube temperature 2020-12-10 18:19:36 +01:00
King Kévin 9fbd4d377e application: minor, add cooling alternative 2020-12-10 18:17:49 +01:00
King Kévin ea5dc74c88 application: minor, fix typo 2020-12-10 18:16:50 +01:00
King Kévin dde56c996c application: extend states and add central management 2020-12-10 18:16:16 +01:00
King Kévin 51cf8b0f95 application: add button 2020-12-08 23:15:53 +01:00
King Kévin 517f58d463 application: limit lid power to 50% 2020-12-08 23:15:21 +01:00
King Kévin 6d93633587 application: recalibrate lid temperature 2020-12-08 23:14:57 +01:00
King Kévin 8b62d16436 application: add bed temperatures (WIP) 2020-12-08 23:13:37 +01:00
King Kévin b071eb6737 application: minor, fix lid names 2020-12-08 23:11:52 +01:00
King Kévin d9229026e5 application: minor, fix documentation 2020-12-08 23:09:46 +01:00
King Kévin b8cc7d7591 application: replace TEC power board with H-bridge 2020-12-08 23:07:36 +01:00
King Kévin eb55880296 application: working WIP firmware to control thermocycler 2020-12-07 19:14:00 +01:00
King Kévin 36456ba45f oled_text: add library to show text on SSD1306 OLED display 2020-12-07 19:12:41 +01:00
King Kévin 89ba66646f font: add graphical font library 2020-12-07 19:11:45 +01:00
King Kévin a107c60e78 sensor_max1247: add library to read ADC values from MAX1247 2020-12-07 19:11:07 +01:00
King Kévin c0a137e546 global: fix ADD_SAFE macro and add function returning sum 2020-12-07 19:10:04 +01:00
King Kévin 685b1450f6 usb_cdcacm: increase buffer size for project 2020-12-07 19:08:32 +01:00
King Kévin 2120adb231 sensor_ds18b20: fix set precision for single device 2020-12-07 19:07:56 +01:00
King Kévin b5a37148d4 sensor_ds18b20: minor, add spacing around operator 2020-12-07 19:07:21 +01:00
King Kévin 4ade159586 onewire_master: set project specific config 2020-12-07 19:05:48 +01:00
King Kévin b9b9eea2a0 ld: use a MCU with 128 KB of flash (e.g. original STM32) 2020-12-07 18:56:33 +01:00
26 changed files with 1869 additions and 623 deletions

321
README.md
View File

@ -1,3 +1,6 @@
This is the firmware for the ThermoHybaid MBS 0.2G MBLK001 issue 2 (short HBMBSG02) thermo-cycler replacement controller board.
This is the firmware for the thermo-cycler
This firmware template is designed for development boards based around [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031).
project
@ -6,50 +9,314 @@ project
summary
-------
*describe project purpose*
I bought a HBMBSG02 thermo-cycler, with unknown defect, and wanted to use to make PCR tests.
technology
----------
*described electronic details*
I replaced the controller board with custom electronics to be able to operate it again.
HBMBSG02
========
The device did power up.
After a couple of seconds it shut down the fan and probably waits for further instructions.
No defect was visible.
I did not get any software to operate it, or check for errors.
This is a rather old device, from 2003.
The manufacturer did not reply to my request for any software or further documentation.
There are two RJ11 ports, for RS-485 communication.
No traffic has been seen during boot up (this probably uses a master slave protocol since the devices can be chained).
I did not get any reply after sending random bytes.
The device is made out of the following parts.
MBLK002
-------
The heating (and cooling) bed for 8x12 PCR tubes.
Made of black anodized aluminium.
lid
---
there is a heated lid with two connectors.
temperature sensor, 12 kOhm NTC thermistor, 3-pin (pin 1 has notch):
1. thermistor lead 1
2. thermistor lead 2
3. chassis
connected to MBLK005.
heating element, 150 Ohm resistor, 2-pin connector.
connected to MBLK078.
this is power by AC 220V, controller by main board.
I don't recommend to have is on at full power for too long.
The heating mat gets hot very fast.
I even blew to inline fuse (196 °C) although I only tried to heat it up to 94 °C.
I replaced the fuse.
MBLK005
-------
this board provides power to the controller board and lid.
power + heater, 2x2 connector:
1. red, 12V (13-77 V)
2. black, ground
3. yellow/red, optocoupler anode for triac controlling the lid heater
4. yellow/black, optocoupler cathode for triac controlling the lid heater
1x2 connector to lid heater, controller by BTA06 triac.
it also allows to select between 110 V and 220 V, and provides AC power to the TEC power supply.
MBLK041
-------
RS485 adapter board.
it provides 2 interconnected RJ11 ports.
this allows to chain thermocyclers.
1x4 connector, to main board:
1. yellow, RS-485-2 A (9/Y on MAX489)
2. green, RS-485-2 B (10/Z on MAX489)
3. brown, RS-485-1 A (12/A on MAX489)
4. orange, RS-485-1 B (11/B on MAX489)
A4-246
------
power supply for the TECs (e.g. peltier elements) though yellow/orange cables.
I think this provides a programmable constant current to operate the TECs efficiently.
2x5 IDC connector to main board:
1. IC2 anode
2. IC2 cathode with R23
3. IC4 anode through R24
4. IC4 anode
5. IC6 collector
6. IC6 emitter
7. IC3 anode
8. IC3 cathode through R15
9. IC5 anode through R25
10. IC5 cathode
by applying any combination on ID2-5 I was not able to have it output power.
IC6 is active when IC5 gets power, but that's the only reaction I got.
test points:
- T3-8 -- TP9 -- TR4 -- TP8
- TP8: 38 kHz @ 1V
- TP9: 38 kHz @ 60V
- TP10: ground
- TP11: PWM 38 kHz @ 13V
- TP12: 6V
- TP13: 16.6V
A4-250
------
H-bridge module mounted on A4-246
PL2 connection:
1. ground
2. orange wire (H-bridge output)
3. yellow wire (H-bridge output)
4. VCC (6V)
PL5:
5. IC3 collector
7. VCC
8. ground
9. ground
10. IC6 anode
12. IC5 collector
13. IC2 emitter (pulled down)
14. IC4 emmiter (pulled down)
front panel
-----------
1x5 connector, to main board:
1. ground
2. play/pause indicator, green LED anode
3. play/pause indicator, orange LED anode
4. power indicator, red LED
5. play/pause button, connected to ground when pressed
fan
---
1x2 connector, to main board:
1. red: 12V
2. black: ground
MBLK019/MBLK020
---------------
controls which TECs get power, and how.
2x3 pin plug (IDC numbering):
1. red, VCC
2. red/green, sink to control IC/TR 2/6
3. red/black, sink to control IC/TR 1/4
4. red/blue, connected to pin 6
5. red/brown, sink to control IC/TR 3/5
6. black, connected to pin 4
1x6 conenctor, to TEC and power supply.
MBLK009
-------
ST 339 is a quad voltage comparator.
I don't know what it compares.
LN 393 is a dual comparator.
I don't know what it compares.
there is also an external reference (REF1).
this is probably for the MAX1247 (only the MAX1246 has an internal voltage reference).
MAX1247 is a 4-channel 12-bit ADC.
it is used to measure the temperatures.
there are four 2.2 kOhm thermistors.
one side is connected to REF1, the other to a MAX1247 channel.
- TH4: on the sink, connected to CH2 through R8 (2702), and COM through R4
- TH3: in the tube, connected to CH3 through R7 (2702), and COM through R3
- in the top of the tub heating bed, connected to CH0
- in the bottom of the tub heating bed, connected to CH1
2x7 IDC connector:
01. NC
02. VCC
03. LN393 OUTPUT-A
04. MAX1247 DIN
05. MAX1247 DOUT
06. MAX1247 SCLK
07. MAX1247 nCS
08. ST339 OUTPUT-3
09. LK1 jumper (missing), other side connected to ground
10. LK2 jumper (missing), other side connected to ground
11. LK3 jumper (present), other side connected to ground
12. LK4 jumper (present), other side connected to ground
13. ground
14. LN393 OUTPUT-A through 10 kOhm resistor (not sure what this is used for, probably not just to pull)
1x6 conenctor to TECs:
- 1 -- free standing 93 °C fuse -- fuse in heat sink -- 2x top TECs in series -- 2
- 2 -- 2x middle-top TECs in series -- 3
- 4 -- 2x middle-bottom TECs in series -- 5
- 5 -- 2x bottom TECs in series -- 6
when 3 is + and 1 is -, the top half bed heats up.
when 6 is + and 4 is -, the bottom half bed heats up.
MBLK005
-------
this is the main controller board.
it is connected to all other parts through individual connectors, or the card edge connector.
the 2.4V rechargeable battery was empty and leaking, with spades corroded.
changing the battery did not change anything: the thermo-cycle goes to sleep after power up.
I don't know it it hold the calibration data in SRAM and the reason why the device has been sold as defective.
This is replaced by a custom controller board for which this firmware is.
board
=====
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
The MBLK005-replacement board uses a [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6.
The underlying template also supports following board:
peripherals
===========
- [Maple Mini](http://leaflabs.com/docs/hardware/maple-mini.html), based on a STM32F103CBT6
- [System Board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#system_board), based on a STM32F103C8T6
- [blue pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6
- [black pill](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#black_pill), based on a STM32F103C8T6
- [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board), based on a STM32F103C8T6
- [ST-LINK V2 mini](https://wiki.cuvoodoo.info/doku.php?id=jtag#mini_st-link_v2), a ST-LINK/V2 clone based on a STM32F101C8T6
- [USB-Blaster](https://wiki.cuvoodoo.info/doku.php?id=jtag#armjishu_usb-blaster), an Altera USB-Blaster clone based on a STM32F101C8T6
additional peripherals to operate the thermo cycler:
**Which board is used is defined in the Makefile**.
This is required to map the user LED and button provided on the board
SSD1306 OLED screen to show the detailed state:
1. GND
2. VDD
3. SCK
4. SDA
The ST-LINK V2 mini clone has SWD test points on the board.
Because read protection is enabled, you will first need to remove the protection to be able to flash the firmware.
To remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
The Altera USB-Blaster clone has a pin header for SWD and UART1 on the board.
SWD is disabled in the main firmware, and it has read protection.
To be able to flash using SWD (or the serial port), the BOOT0 pin must be set to 1 to boot the system memory install of the flash memory.
To set BOOT0 to 1, apply 3.3 V on R11, between the resistor and the reference designator, when powering the device.
The red LED should stay off while the green LED is on.
Now you can remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
24V 12A power supply replacing the A4-246.
ideally it would be an adjustable constant current supply (probably what the original power supply was), but using PWM on constant voltage is good enough for thermocouples, even if less efficient.
12V is not enough to heat the TECs rapidly up to 94 °C.
connections
===========
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
- *list board to peripheral pin connections*
All pins are configured using `define`s in the corresponding source code.
Connect the peripherals the following way.
heating block MBLK-008 CO IDC 7x2:
01. NC
02. 3.3V
03. PB3
04. PB15 SPI2_MOSI
05. PB14 SPI2_MISO
06. PB13 SPI2_SCK
07. PB12 SPI2_NSS
08. PB4
09. PB5
10. PC14
11. PC15
12. PB1
13. ground
heated lid, 12 kOhm NTC thermistor, 3-pin (pin 1 has notch):
1. PA0/ADC1_CH0, pulled up to 5.0V by 10 kOhm resistor
2. ground
3. earth
thermocouple controller board, MBLK019, 2x3 IDC connector:
1. 3.3 V
2. PA1
3. PA2
4. PA3
5. PA4
6. ground
front panel:
1. ground
2. PA5, with 330 Ohm inline resistor
3. PA6, with 330 Ohm inline resistor
4. PA7, with 330 Ohm inline resistor
5. PB0
fan:
1. 12V front board power supply
2. power nMOS drain
power nMOS source. ground
power nMOS gate. PA15, pulled up externally to 5V
MBLK-078, power + heater, 2x2 connector:
1. LM7805 in
2. ground
3. 5V though 330 Ohm
4. PA10
SSD1306 OLED screen:
1. ground
2. 3.3 V
3. PB6/I2C1_SCL, pulled up to 3.3V by external 10 kOhm resistor
4. PB7/I2C1_SDA, pulled up to 3.3V by external 10 kOhm resistor
thermocouple power supply control board. IDC 2x5:
- 1,2: 5V
- 3,4: PB10
- 5,6: PB11
- 7,8: PB9
- 9,10: ground
DS18B20 1-Wire temperature sensor, 1x3:
1. OWD, PB8
2. ground
3. 3.3V
code
====

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
/* define memory regions. */
MEMORY
{
rom (rx) : ORIGIN = 0x08000000 + 8K, LENGTH = 64K - 8K
rom (rx) : ORIGIN = 0x08000000 + 8K, LENGTH = 128K - 8K
ram (rwx) : ORIGIN = 0x20000000 + 4, LENGTH = 20K - 4
}
PROVIDE(__application_beginning = ORIGIN(rom));
@ -21,8 +21,8 @@ PROVIDE(__flash_end = 0);
PROVIDE(__application_end = ORIGIN(rom) + LENGTH(rom));
PROVIDE(__flash_end = ORIGIN(rom) + LENGTH(rom));
*/
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
PROVIDE(__application_end = ORIGIN(rom) + LENGTH(rom));
PROVIDE(__flash_end = ORIGIN(rom) + LENGTH(rom));
/* RAM location reserved so application can talk to bootloader and tell to start DFU */
PROVIDE(__dfu_magic = ORIGIN(ram) - 4);

View File

@ -83,7 +83,7 @@ void main(void)
(*(void(**)(void))((uint32_t)application + 4))(); // start application (by jumping to the reset function which address is stored as second entry of the vector table)
}
rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]); // start main clock
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // start main clock
board_setup(); // setup board to control LED
led_on(); // indicate bootloader started
#if defined(BUSVOODOO)

View File

@ -21,8 +21,8 @@ PROVIDE(__flash_end = 0);
PROVIDE(__application_end = ORIGIN(rom) + LENGTH(rom));
PROVIDE(__flash_end = ORIGIN(rom) + LENGTH(rom));
*/
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
PROVIDE(__application_end = ORIGIN(rom) + 128K);
PROVIDE(__flash_end = ORIGIN(rom) + 128K);
/* RAM location reserved so application can talk to bootloader and tell to start DFU */
PROVIDE(__dfu_magic = ORIGIN(ram) - 4);

View File

@ -711,7 +711,7 @@ int32_t adds32_safe(int32_t a, int32_t b);
/** symbol for beginning of the application
* @note this symbol will be provided by the linker script
*/
extern uint32_t __application_beginning;
extern char __application_beginning;
/** symbol for end of the application
* @note this symbol will be provided by the linker script
*/

View File

@ -37,7 +37,7 @@ static void flash_internal_init(void)
if ((uint32_t)&__flash_end >= FLASH_BASE) {
flash_internal_end = (uint32_t)&__flash_end;
} else {
flash_internal_end = FLASH_BASE + desig_get_flash_size() * 1024;
flash_internal_end = FLASH_BASE + DESIG_FLASH_SIZE * 1024;
}
}
}
@ -70,7 +70,7 @@ static bool flash_internal_range(uint32_t address, size_t size)
uint16_t flash_internal_page_size(void)
{
if (0 == flash_internal_page) { // we don't know the page size yet
if (desig_get_flash_size() < 256) {
if (DESIG_FLASH_SIZE < 256) {
if ((*(uint32_t*)0x1FFFF000 & 0xFFFE0000) == 0x20000000) { // non-connectivity system memory start detected (MSP address pointing to SRAM
flash_internal_page = 1024;
} else { // connectivity system memory start is at 0x1FFFB000
@ -224,7 +224,7 @@ void flash_internal_eeprom_setup(uint16_t pages)
flash_internal_eeprom_start = 0; // reset start address
flash_internal_eeprom_address = 0; // reset EEPROM address
if (pages > desig_get_flash_size() * 1024 / flash_internal_page) { // not enough pages are available
if (pages > DESIG_FLASH_SIZE * 1024 / flash_internal_page) { // not enough pages are available
return;
}
flash_internal_eeprom_start = flash_internal_end - flash_internal_page * pages; // set EEPROM start (page aligned)
@ -313,7 +313,7 @@ uint32_t flash_internal_probe_read_size(void)
uint32_t flash_internal_probe_write_size(void)
{
if (0 == desig_get_flash_size()) { // no flash size advertised
if (0 == DESIG_FLASH_SIZE) { // no flash size advertised
return 0;
}
@ -324,7 +324,7 @@ uint32_t flash_internal_probe_write_size(void)
// prepare for writing the flash
flash_unlock(); // unlock flash to be able to write it
// try reading and writing the flash, page per page
uint32_t start = FLASH_BASE + desig_get_flash_size() * 1024; // start with the end of the advertised flash
uint32_t start = FLASH_BASE + DESIG_FLASH_SIZE * 1024; // start with the end of the advertised flash
if ((uint32_t)&__flash_end >= FLASH_BASE) { // use linker flash size if provided
start = (uint32_t)&__flash_end;
}

View File

@ -1,8 +1,21 @@
/* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** monospace pixel fonts collection (code)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdint.h> // standard integer types
#include "font.h" // own definitions

View File

@ -1,7 +1,20 @@
/* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** monospace pixel fonts collection (API)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2018
*/
#pragma once

View File

@ -183,7 +183,7 @@ void i2c_master_setup(uint32_t i2c, uint16_t frequency)
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // setup I²C I/O pins
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // already put signal high to avoid small pulse
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // setup I²C I/O pins
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // setup I²C I/O pins
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function
rcc_periph_clock_enable(RCC_I2C(i2c)); // enable clock for I²C peripheral
i2c_reset(i2c); // reset peripheral domain
@ -271,7 +271,7 @@ bool i2c_master_reset(uint32_t i2c)
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high (try second transition)
to_return &= !gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is high
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // set I²C I/O pins back
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // set I²C I/O pins back
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // set I²C I/O pins back
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset device
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // reset device
i2c_peripheral_enable(i2c); // re-enable device

View File

@ -1,8 +1,21 @@
/* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to show display text on SSD1306 OLED display
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2020
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @note peripherals used: I²C @ref oled_ssd1306_i2c
*/
/* standard libraries */

View File

@ -1,8 +1,21 @@
/* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to show display text on SSD1306 OLED display
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018-2020
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @note peripherals used: I²C @ref oled_ssd1306_i2c
*/
#include "font.h"

View File

@ -29,13 +29,13 @@
* @note external pull-up resistor on pin is required (< 5 kOhm)
* @{
*/
#define ONEWIRE_MASTER_PIN PC9 /**< GPIO pin */
#define ONEWIRE_MASTER_PIN PB8 /**< GPIO pin */
/** @} */
/** @defgroup onewire_master_timer timer used to measure 1-wire signal timing
* @{
*/
#define ONEWIRE_MASTER_TIMER 5 /**< timer ID */
#define ONEWIRE_MASTER_TIMER 3 /**< timer ID */
/** @} */
/** set if the timer ISR should be set in the interrupt table instead of the vector table

View File

@ -1,18 +1,18 @@
/** library to send data using ESP8266 WiFi SoC
/** library to send data using ESP8266 WiFi SoC (code)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2021
* @date 2016
* @note peripherals used: USART @ref radio_esp8266_usart
*/
// standard libraries
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <string.h> // string and memory utilities
#include <stdio.h> // string utilities
// STM32 (including CM3) libraries
/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/usart.h> // universal synchronous asynchronous receiver transmitter library
@ -26,218 +26,135 @@
* @{
*/
#define RADIO_ESP8266_USART 2 /**< USART peripheral */
#define RADIO_ESP8266_TX PA2 /**< pin used for USART TX */
#define RADIO_ESP8266_RX PA3 /**< pin used for USART RX */
#define RADIO_ESP8266_AF GPIO_AF7 /**< alternate function for UART pins */
/** @} */
// input and output buffers and used memory
/* input and output buffers and used memory */
static uint8_t rx_buffer[24] = {0}; /**< buffer for received data (we only expect AT responses) */
static volatile uint16_t rx_used = 0; /**< number of byte in receive buffer */
static uint8_t tx_buffer[256] = {0}; /**< buffer for data to transmit */
static volatile uint16_t tx_used = 0; /**< number of bytes used in transmit buffer */
// response status
volatile bool radio_esp8266_response = false; /**< when a response has been received (OK or ERROR) */
volatile bool radio_esp8266_success = false; /**< if the response is OK (else ERROR), set when radio_esp8266_response is set to true */
volatile bool radio_esp8266_activity = false;
volatile bool radio_esp8266_success = false;
/** transmit data to radio
* @param[in] data data to transmit
* @param[in] length length of data to transmit
*/
static void radio_esp8266_transmit(const uint8_t* data, uint8_t length) {
static void radio_esp8266_transmit(uint8_t* data, uint8_t length) {
while (tx_used || !usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // wait until ongoing transmission completed
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable transmit interrupt
__WFI(); // sleep until something happened
}
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // ensure transmit interrupt is disable to prevent index corruption (the ISR should already have done it)
radio_esp8266_response = false; // reset status because of new activity
for (tx_used = 0; tx_used < length && tx_used < LENGTH(tx_buffer); tx_used++) { // copy data
tx_buffer[tx_used] = data[length - 1 - tx_used]; // put character in buffer (in reverse order)
radio_esp8266_activity = false; // reset status because of new activity
for (tx_used=0; tx_used<length && tx_used<LENGTH(tx_buffer); tx_used++) { // copy data
tx_buffer[tx_used] = data[length-1-tx_used]; // put character in buffer (in reverse order)
}
if (tx_used) {
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable interrupt to send bytes
}
}
bool radio_esp8266_setup(void)
void radio_esp8266_setup(void)
{
// configure pins
rcc_periph_clock_enable(GPIO_RCC(RADIO_ESP8266_TX)); // enable clock for USART TX pin port peripheral
gpio_mode_setup(GPIO_PORT(RADIO_ESP8266_TX), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(RADIO_ESP8266_TX)); // set TX pin to alternate function
gpio_set_output_options(GPIO_PORT(RADIO_ESP8266_TX), GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN(RADIO_ESP8266_TX)); // set TX pin output as push-pull
gpio_set_af(GPIO_PORT(RADIO_ESP8266_TX), RADIO_ESP8266_AF, GPIO_PIN(RADIO_ESP8266_TX)); // set alternate function to USART
rcc_periph_clock_enable(GPIO_RCC(RADIO_ESP8266_RX)); // enable clock for USART RX pin port peripheral
gpio_mode_setup(GPIO_PORT(RADIO_ESP8266_RX), GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN(RADIO_ESP8266_RX)); // set GPIO to alternate function, with pull up to avoid noise in case it is not connected
gpio_set_af(GPIO_PORT(RADIO_ESP8266_RX), RADIO_ESP8266_AF, GPIO_PIN(RADIO_ESP8266_RX)); // set alternate function to USART
/* enable USART I/O peripheral */
rcc_periph_clock_enable(RCC_AFIO); // enable pin alternate function (USART)
rcc_periph_clock_enable(USART_PORT_RCC(RADIO_ESP8266_USART)); // enable clock for USART port peripheral
rcc_periph_clock_enable(USART_RCC(RADIO_ESP8266_USART)); // enable clock for USART peripheral
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, USART_PIN_TX(RADIO_ESP8266_USART)); // setup GPIO pin USART transmit
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, USART_PIN_RX(RADIO_ESP8266_USART)); // setup GPIO pin USART receive
gpio_set(USART_PORT(RADIO_ESP8266_USART), USART_PIN_RX(RADIO_ESP8266_USART)); // pull up to avoid noise when not connected
// configure USART
rcc_periph_clock_enable(RCC_USART(RADIO_ESP8266_USART)); // enable clock for USART peripheral
rcc_periph_reset_pulse(RST_USART(RADIO_ESP8266_USART)); // reset peripheral
usart_set_baudrate(USART(RADIO_ESP8266_USART), 115200);
/* setup USART parameters for ESP8266 AT firmware */
usart_set_baudrate(USART(RADIO_ESP8266_USART), 115200); // AT firmware 0.51 (SDK 1.5.0) uses 115200 bps
usart_set_databits(USART(RADIO_ESP8266_USART), 8);
usart_set_stopbits(USART(RADIO_ESP8266_USART), USART_STOPBITS_1);
usart_set_mode(USART(RADIO_ESP8266_USART), USART_MODE_TX_RX);
usart_set_parity(USART(RADIO_ESP8266_USART), USART_PARITY_NONE);
usart_set_flow_control(USART(RADIO_ESP8266_USART), USART_FLOWCONTROL_NONE);
nvic_enable_irq(USART_IRQ(RADIO_ESP8266_USART)); // enable the UART interrupt
nvic_enable_irq(USART_IRQ(RADIO_ESP8266_USART)); // enable the USART interrupt
usart_enable_rx_interrupt(USART(RADIO_ESP8266_USART)); // enable receive interrupt
usart_enable(USART(RADIO_ESP8266_USART)); // enable UART
usart_enable(USART(RADIO_ESP8266_USART)); // enable USART
// reset buffer states
/* reset buffer states */
rx_used = 0;
tx_used = 0;
radio_esp8266_activity = false;
radio_esp8266_success = false;
// verify if ESP8266 is reachable
uint16_t timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)"AT\r\n", 4); // verify if module is present
while (!radio_esp8266_response) { // wait for response
if (timeout > 100) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
radio_esp8266_transmit((uint8_t*)"AT\r\n",4); // verify if module is present
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) {
return false;
radio_esp8266_transmit((uint8_t*)"AT+RST\r\n",8); // reset module
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
// reset module so it connects to AP
timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)"AT+RST\r\n", 8); // reset module
while (!radio_esp8266_response) { // wait for response
if (timeout > 100) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
while(rx_used<13 || memcmp((char*)&rx_buffer[rx_used-13], "WIFI GOT IP\r\n", 13)!=0) { // wait to have IP
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) {
return false;
radio_esp8266_transmit((uint8_t*)"ATE0\r\n",6); // disable echoing
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
timeout = 0; // reset timeout counter
while(rx_used < 13 || 0 != memcmp((char*)&rx_buffer[rx_used - 13], "WIFI GOT IP\r\n", 13)) { // wait to have IP
if (timeout > 10000) { // connection takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
// disable echo for better parsing
timeout = 0; // reset timeout counter
while (!radio_esp8266_response) { // wait for response
if (timeout > 100) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
return false;
}
return true;
}
bool radio_esp8266_open(const char* host, uint16_t port, bool tcp)
void radio_esp8266_tcp_open(char* host, uint16_t port)
{
char command[256] = {0}; // string to create command
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"%s\",\"%s\",%u\r\n", tcp ? "TCP" : "UDP", host, port); // create AT command to establish a TCP connection
if (length > 0) {
uint16_t timeout = 0; // reset timeout counter
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"TCP\",\"%s\",%u\r\n", host, port); // create AT command to establish a TCP connection
if (length>0) {
radio_esp8266_transmit((uint8_t*)command, length);
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
return false;
}
}
return true;
}
bool radio_esp8266_send(const uint8_t* data, uint8_t length)
void radio_esp8266_send(uint8_t* data, uint8_t length)
{
char command[16 + 1] = {0}; // string to create command
char command[16+1] = {0}; // string to create command
int command_length = snprintf(command, LENGTH(command), "AT+CIPSEND=%u\r\n", length); // create AT command to send data
if (command_length > 0) {
// start sending
uint16_t timeout = 0; // reset timeout counter
if (command_length>0) {
radio_esp8266_transmit((uint8_t*)command, command_length); // transmit AT command
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) {
return false;
if (!radio_esp8266_success) { // send AT command did not succeed
return; // don't transmit data
}
// send actual data
timeout = 0; // reset timeout counter
radio_esp8266_transmit(data, length); // transmit data
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
return false;
}
}
return true;
}
bool radio_esp8266_close(void)
void radio_esp8266_close(void)
{
uint16_t timeout = 0; // reset timeout counter
radio_esp8266_transmit((uint8_t*)"AT+CIPCLOSE\r\n", 13); // send AT command to close established connection
while (!radio_esp8266_response) { // wait for response
if (timeout > 1000) { // response takes too long
return false;
}
sleep_ms(10); // wait a tiny bit
timeout += 10; // remember we waited
}
if (!radio_esp8266_success) {
return false;
}
return true;
}
/** USART interrupt service routine called when data has been transmitted or received */
void USART_ISR(RADIO_ESP8266_USART)(void)
{
if (usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // data has been transmitted
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // data has been transmitted
if (tx_used) { // there is still data in the buffer to transmit
usart_send(USART(RADIO_ESP8266_USART), tx_buffer[tx_used - 1]); // put data in transmit register
usart_send(USART(RADIO_ESP8266_USART),tx_buffer[tx_used-1]); // put data in transmit register
tx_used--; // update used size
} else { // no data in the buffer to transmit
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // disable transmit interrupt
}
}
if (usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_RXNE)) { // data has been received
while (rx_used >= LENGTH(rx_buffer)) { // if buffer is full
memmove(rx_buffer, &rx_buffer[1], LENGTH(rx_buffer) - 1); // drop old data to make space (ring buffer are more efficient but harder to handle)
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_RXNE)) { // data has been received
while (rx_used>=LENGTH(rx_buffer)) { // if buffer is full
memmove(rx_buffer,&rx_buffer[1],LENGTH(rx_buffer)-1); // drop old data to make space (ring buffer are more efficient but harder to handle)
rx_used--; // update used buffer information
}
rx_buffer[rx_used++] = usart_recv(USART(RADIO_ESP8266_USART)); // put character in buffer
// if the used send a packet with these strings during the commands detection the AT command response will break (AT commands are hard to handle perfectly)
if (rx_used >= 4 && 0 == memcmp((char*)&rx_buffer[rx_used - 4], "OK\r\n", 4)) { // OK received
radio_esp8266_response = true; // response received
if (rx_used>=4 && memcmp((char*)&rx_buffer[rx_used-4], "OK\r\n", 4)==0) { // OK received
radio_esp8266_activity = true; // response received
radio_esp8266_success = true; // command succeeded
rx_used = 0; // reset buffer
} else if (rx_used >= 7 && 0 == memcmp((char*)&rx_buffer[rx_used - 7], "ERROR\r\n", 7)) { // ERROR received
radio_esp8266_response = true; // response received
} else if (rx_used>=7 && memcmp((char*)&rx_buffer[rx_used-7], "ERROR\r\n", 7)==0) { // ERROR received
radio_esp8266_activity = true; // response received
radio_esp8266_success = false; // command failed
rx_used = 0; // reset buffer
}

View File

@ -2,7 +2,7 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2021
* @date 2016
* @note peripherals used: USART @ref radio_esp8266_usart
*/
#pragma once
@ -13,23 +13,22 @@ extern volatile bool radio_esp8266_activity;
extern volatile bool radio_esp8266_success;
/** setup peripherals to communicate with radio
* @return if it connected to AP
* @note this is blocking to ensure we are connected to the WiFi network
*/
bool radio_esp8266_setup(void);
void radio_esp8266_setup(void);
/** establish TCP connection
* @param[in] host host to connect to
* @param[in] port port number to connect to
* @param[in] tcp if connect to a TCP port (else UDP)
* @return if operation succeeded
* @param[in] port TCP port to connect to
* @note wait for activity to get success status
*/
bool radio_esp8266_open(const char* host, uint16_t port, bool tcp);
void radio_esp8266_tcp_open(char* host, uint16_t port);
/** send data (requires established connection)
* @param[in] data data to send
* @param[in] length size of data to send
* @return if operation succeeded
* @note wait for activity to get success status
*/
bool radio_esp8266_send(const uint8_t* data, uint8_t length);
void radio_esp8266_send(uint8_t* data, uint8_t length);
/** close established connection
* @return if operation succeeded
* @note wait for activity to get success status
*/
bool radio_esp8266_close(void);
void radio_esp8266_close(void);

177
lib/sensor_dht11.c Normal file
View File

@ -0,0 +1,177 @@
/** library to query measurements from Aosong DHT11 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht11_timer
*/
/* standard libraries */
#include <stdint.h> // standard integer types
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "sensor_dht11.h" // PZEM electricity meter header and definitions
#include "global.h" // common methods
/** @defgroup sensor_dht11_timer timer peripheral used to measure signal timing for bit decoding
* @{
*/
#define SENSOR_DHT11_TIMER 3 /**< timer peripheral */
#define SENSOR_DHT11_CHANNEL 1 /**< channel used as input capture */
#define SENSOR_DHT11_JITTER 0.1 /**< signal timing jitter tolerated in timing */
/** @} */
volatile bool sensor_dht11_measurement_received = false;
/** communication states */
volatile enum sensor_dht11_state_t {
SENSOR_DHT11_OFF, // no request has started
SENSOR_DHT11_HOST_START, // host starts request (and waits >18ms)
SENSOR_DHT11_HOST_STARTED, // host started request and waits for slave answer
SENSOR_DHT11_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
SENSOR_DHT11_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
SENSOR_DHT11_MAX
} sensor_dht11_state = SENSOR_DHT11_OFF; /**< current communication state */
/** the bit number being sent (MSb first), up to 40 */
volatile uint8_t sensor_dht11_bit = 0;
/** the 40 bits (5 bytes) being sent by the device */
volatile uint8_t sensor_dht11_bits[5] = {0};
/** reset all states */
static void sensor_dht11_reset(void)
{
// reset states
sensor_dht11_state = SENSOR_DHT11_OFF;
sensor_dht11_bit = 0;
sensor_dht11_measurement_received = false;
gpio_set(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // setup GPIO pin as output (host starts communication before slave replies)
timer_ic_disable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL)); // enable capture interrupt only when receiving data
timer_disable_counter(TIM(SENSOR_DHT11_TIMER)); // disable timer
}
void sensor_dht11_setup(void)
{
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
rcc_periph_clock_enable(RCC_TIM_CH(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // enable clock for GPIO peripheral
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT11_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_DHT11_TIMER)); // reset timer state
timer_set_mode(TIM(SENSOR_DHT11_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
timer_set_prescaler(TIM(SENSOR_DHT11_TIMER), 20 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(72E6/20/(2**16))=18.20ms )
timer_ic_set_input(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT11_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_polarity(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_FALLING); // capture on rising edge
timer_ic_set_prescaler(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(SENSOR_DHT11_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_DHT11_TIMER), TIM_DIER_CCIE(SENSOR_DHT11_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT11_TIMER)); // catch interrupt in service routine
sensor_dht11_reset(); // reset state
}
bool sensor_dht11_measurement_request(void)
{
if (sensor_dht11_state != SENSOR_DHT11_OFF) { // not the right state to start (wait up until timeout to reset state)
return false;
}
if (gpio_get(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)) == 0) { // signal should be high per default
return false;
}
if (TIM_CR1(TIM(SENSOR_DHT11_TIMER)) & (TIM_CR1_CEN)) { // timer should be off
return false;
}
sensor_dht11_reset(); // reset states
// send start signal (pull low for > 18 ms)
gpio_clear(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // set signal to low
timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(SENSOR_DHT11_TIMER)); // enable timer to wait for 18 ms until overflow
sensor_dht11_state = SENSOR_DHT11_HOST_START; // remember we started sending signal
return true;
}
struct sensor_dht11_measurement_t sensor_dht11_measurement_decode(void)
{
struct sensor_dht11_measurement_t measurement = { 0xff, 0xff }; // measurement to return
if (sensor_dht11_bit < 40) { // not enough bits received
return measurement;
}
if ((uint8_t)(sensor_dht11_bits[0] + sensor_dht11_bits[1] + sensor_dht11_bits[2] + sensor_dht11_bits[3]) != sensor_dht11_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
return measurement;
}
// calculate measured values (byte 1 and 3 should be the factional value but they are always 0)
measurement.humidity = sensor_dht11_bits[0];
measurement.temperature = sensor_dht11_bits[2];
return measurement;
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_DHT11_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_UIF); // clear flag
if (sensor_dht11_state == SENSOR_DHT11_HOST_START) { // start signal sent
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL)); // switch pin to input (the external pull up with also set the signal high)
sensor_dht11_state = SENSOR_DHT11_HOST_STARTED; // switch to next state
timer_ic_enable(TIM(SENSOR_DHT11_TIMER), TIM_IC(SENSOR_DHT11_CHANNEL)); // enable capture interrupt only when receiving data
} else { // timeout occurred
sensor_dht11_reset(); // reset states
}
} else if (timer_get_flag(TIM(SENSOR_DHT11_TIMER), TIM_SR_CCIF(SENSOR_DHT11_CHANNEL))) { // edge detected on input capture
uint16_t time = TIM_CCR(SENSOR_DHT11_TIMER,SENSOR_DHT11_CHANNEL); // save captured bit timing (this clear also the flag)
timer_set_counter(TIM(SENSOR_DHT11_TIMER), 0); // reset timer counter
time = (time * 1E6) / (rcc_ahb_frequency / (TIM_PSC(TIM(SENSOR_DHT11_TIMER)) + 1)); // calculate time in us
switch (sensor_dht11_state) {
case (SENSOR_DHT11_HOST_STARTED): // the host query data and the slave is responding
sensor_dht11_state = SENSOR_DHT11_SLAVE_START; // set new state
break;
case (SENSOR_DHT11_SLAVE_START): // the slave sent the start signal
if (time >= ((80 + 80) * (1 - SENSOR_DHT11_JITTER)) && time <= ((80 + 80)*(1 + SENSOR_DHT11_JITTER))) { // response time should be 80 us low and 80 us high
sensor_dht11_state = SENSOR_DHT11_SLAVE_BIT; // set new state
} else {
goto error;
}
break;
case (SENSOR_DHT11_SLAVE_BIT): // the slave sent a bit
if (sensor_dht11_bit >= 40) { // no bits should be received after 40 bits
goto error;
}
if (time >= ((50 + 26) * (1 - SENSOR_DHT11_JITTER)) && time <= ((50 + 28) * (1 + SENSOR_DHT11_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
sensor_dht11_bits[sensor_dht11_bit / 8] &= ~(1 << (7 - (sensor_dht11_bit % 8))); // clear bit
} else if (time >= ((50 + 70)*(1 - SENSOR_DHT11_JITTER)) && time <= ((50 + 70) * (1 + SENSOR_DHT11_JITTER))) { // bit 1 time should be 50 us low and 70 us high
sensor_dht11_bits[sensor_dht11_bit / 8] |= (1 << (7 - (sensor_dht11_bit % 8))); // set bit
} else {
goto error;
}
sensor_dht11_bit++;
if (sensor_dht11_bit >= 40) { // all bits received
sensor_dht11_reset(); // reset states
sensor_dht11_bit = 40; // signal decoder all bits have been received
sensor_dht11_measurement_received = true; // signal user all bits have been received
}
break;
default: // unexpected state
error:
sensor_dht11_reset(); // reset states
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

28
lib/sensor_dht11.h Normal file
View File

@ -0,0 +1,28 @@
/** library to query measurements from Aosong DHT11 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht11_timer (add external pull-up resistor)
*/
#pragma once
/** a measurement response has been received */
extern volatile bool sensor_dht11_measurement_received;
/** measurement returned by sensor */
struct sensor_dht11_measurement_t {
uint8_t humidity; /**< relative humidity in %RH (20-95) */
uint8_t temperature; /**< temperature in °C (0-50) */
};
/** setup peripherals to communicate with sensor */
void sensor_dht11_setup(void);
/** request measurement from sensor
* @return request started successfully
*/
bool sensor_dht11_measurement_request(void);
/** decode received measurement
* @return decoded measurement (0xff,0xff if invalid)
*/
struct sensor_dht11_measurement_t sensor_dht11_measurement_decode(void);

View File

@ -1,206 +0,0 @@
/** library to query measurements from Aosong and DHT22/AM2302 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht1122_timer
*/
/* standard libraries */
#include <stdint.h> // standard integer types
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "sensor_dht1122.h" // PZEM electricity meter header and definitions
#include "global.h" // common methods
/** @defgroup sensor_dht1122_timer timer peripheral used to measure signal timing for bit decoding
* @{
*/
#define SENSOR_DHT1122_PIN PB4 /**< MCU pin connected to DHT1122 I/O pin, pulled up externally */
#define SENSOR_DHT1122_TIMER 3 /**< timer peripheral for DHT1122 pin */
#define SENSOR_DHT1122_CHANNEL 1 /**< channel used as input capture */
#define SENSOR_DHT1122_AF GPIO_AF2 /**< pin alternate function to use as timer */
#define SENSOR_DHT1122_JITTER 0.2 /**< signal timing jitter tolerated in timing */
/** @} */
volatile bool sensor_dht1122_measurement_received = false;
/** remember if the sensor is a DHT11 or DHT22 */
static bool sensor_dht1122_dht22 = false;
/** communication states */
volatile enum sensor_dht1122_state_t {
SENSOR_DHT1122_OFF, // no request has started
SENSOR_DHT1122_HOST_START, // host starts request (and waits >18ms)
SENSOR_DHT1122_HOST_STARTED, // host started request and waits for slave answer
SENSOR_DHT1122_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
SENSOR_DHT1122_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
SENSOR_DHT1122_MAX
} sensor_dht1122_state = SENSOR_DHT1122_OFF; /**< current communication state */
/** the bit number being sent (MSb first), up to 40 */
volatile uint8_t sensor_dht1122_bit = 0;
/** the 40 bits (5 bytes) being sent by the device */
volatile uint8_t sensor_dht1122_bits[5] = {0};
/** reset all states */
static void sensor_dht1122_reset(void)
{
// reset states
sensor_dht1122_state = SENSOR_DHT1122_OFF;
sensor_dht1122_bit = 0;
sensor_dht1122_measurement_received = false;
gpio_set(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
gpio_mode_setup(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_DHT1122_PIN)); // setup GPIO pin as output (host starts communication before slave replies)
timer_ic_disable(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL)); // disable capture interrupt only when receiving data
timer_disable_counter(TIM(SENSOR_DHT1122_TIMER)); // disable timer
}
void sensor_dht1122_setup(bool dht22)
{
sensor_dht1122_dht22 = dht22; // remember sensor type
// configure pin to use as timer input
rcc_periph_clock_enable(GPIO_RCC(SENSOR_DHT1122_PIN)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // already put signal high
gpio_mode_setup(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_DHT1122_PIN)); // set pin to alternate function
gpio_set_output_options(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(SENSOR_DHT1122_PIN)); // set pin output as open-drain
gpio_set_af(GPIO_PORT(SENSOR_DHT1122_PIN), SENSOR_DHT1122_AF, GPIO_PIN(SENSOR_DHT1122_PIN)); // set alternate function to pin
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT1122_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_DHT1122_TIMER)); // reset timer state
timer_set_mode(TIM(SENSOR_DHT1122_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
timer_enable_break_main_output(TIM(SENSOR_DHT1122_TIMER)); // required to enable some timer, even when no dead time is used
// one difference between DHT11 and DHT22 is the request pulse duration
if (!sensor_dht1122_dht22) { // for DHT11
timer_set_prescaler(TIM(SENSOR_DHT1122_TIMER), 24 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(84E6/24/(2**16)) = 18.7 ms )
} else { // for DHT22
timer_set_prescaler(TIM(SENSOR_DHT1122_TIMER), 2 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(84E6/2/(2**16)) = 1.56 ms )
}
timer_ic_set_input(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT1122_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_polarity(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_FALLING); // capture on falling edge (start of bit)
timer_ic_set_prescaler(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_clear_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(SENSOR_DHT1122_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(SENSOR_DHT1122_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_CCIF(SENSOR_DHT1122_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_DHT1122_TIMER), TIM_DIER_CCIE(SENSOR_DHT1122_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT1122_TIMER)); // catch interrupt in service routine
sensor_dht1122_reset(); // reset state
}
bool sensor_dht1122_measurement_request(void)
{
if (sensor_dht1122_state != SENSOR_DHT1122_OFF) { // not the right state to start (wait up until timeout to reset state)
return false;
}
if (gpio_get(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)) == 0) { // signal should be high per default
return false;
}
if (TIM_CR1(TIM(SENSOR_DHT1122_TIMER)) & (TIM_CR1_CEN)) { // timer should be off
return false;
}
sensor_dht1122_reset(); // reset states
// send start signal (pull low for > 18 ms)
gpio_clear(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // set signal to low
timer_set_counter(TIM(SENSOR_DHT1122_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(SENSOR_DHT1122_TIMER)); // enable timer to wait for 18 ms until overflow
sensor_dht1122_state = SENSOR_DHT1122_HOST_START; // remember we started sending signal
return true;
}
struct sensor_dht1122_measurement_t sensor_dht1122_measurement_decode(void)
{
struct sensor_dht1122_measurement_t measurement = { 0xff, 0xff }; // measurement to return
if (sensor_dht1122_bit < 40) { // not enough bits received
return measurement;
}
if ((uint8_t)(sensor_dht1122_bits[0] + sensor_dht1122_bits[1] + sensor_dht1122_bits[2] + sensor_dht1122_bits[3]) != sensor_dht1122_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
return measurement;
}
if (0 == sensor_dht1122_bits[0] && 0 == sensor_dht1122_bits[1] && 0 == sensor_dht1122_bits[2] && 0 == sensor_dht1122_bits[3] && 0 == sensor_dht1122_bits[4]) { // this is measurement is very unlikely, there must be an error
return measurement;
}
// the other difference between DHT11 and DHT22 is the encoding of the values
if (!sensor_dht1122_dht22) { // for DHT11
// calculate measured values (byte 1 and 3 should be the factional value but they are always 0)
measurement.humidity = sensor_dht1122_bits[0];
measurement.temperature = sensor_dht1122_bits[2];
} else { // for DHT22
measurement.humidity = (int16_t)((sensor_dht1122_bits[0] << 8) + sensor_dht1122_bits[1]) / 10.0;
measurement.temperature = (int16_t)((sensor_dht1122_bits[2] << 8) + sensor_dht1122_bits[3]) / 10.0;
}
return measurement;
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_DHT1122_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_UIF); // clear flag
if (sensor_dht1122_state == SENSOR_DHT1122_HOST_START) { // start signal sent
gpio_set(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_PIN(SENSOR_DHT1122_PIN)); // let pin go so we can use it as input
gpio_mode_setup(GPIO_PORT(SENSOR_DHT1122_PIN), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(SENSOR_DHT1122_PIN)); // set pin to alternate function so the timer can read the pin
sensor_dht1122_state = SENSOR_DHT1122_HOST_STARTED; // switch to next state
timer_ic_enable(TIM(SENSOR_DHT1122_TIMER), TIM_IC(SENSOR_DHT1122_CHANNEL)); // enable capture interrupt only when receiving data
} else { // timeout occurred
sensor_dht1122_reset(); // reset states
}
} else if (timer_get_flag(TIM(SENSOR_DHT1122_TIMER), TIM_SR_CCIF(SENSOR_DHT1122_CHANNEL))) { // edge detected on input capture
uint16_t time = TIM_CCR(SENSOR_DHT1122_TIMER, SENSOR_DHT1122_CHANNEL); // save captured bit timing (this clear also the flag)
timer_set_counter(TIM(SENSOR_DHT1122_TIMER), 0); // reset timer counter
time = (time * 1E6) / (rcc_ahb_frequency / (TIM_PSC(TIM(SENSOR_DHT1122_TIMER)) + 1)); // calculate time in us
switch (sensor_dht1122_state) {
case SENSOR_DHT1122_HOST_STARTED: // the host query data and the slave is responding
sensor_dht1122_state = SENSOR_DHT1122_SLAVE_START; // set new state
break;
case SENSOR_DHT1122_SLAVE_START: // the slave sent the start signal
if (time >= ((80 + 80) * (1 - SENSOR_DHT1122_JITTER)) && time <= ((80 + 80) * (1 + SENSOR_DHT1122_JITTER))) { // response time should be 80 us low and 80 us high
sensor_dht1122_state = SENSOR_DHT1122_SLAVE_BIT; // set new state
} else {
goto error;
}
break;
case SENSOR_DHT1122_SLAVE_BIT: // the slave sent a bit
if (sensor_dht1122_bit >= 40) { // no bits should be received after 40 bits
goto error;
}
if (time >= ((50 + 26) * (1 - SENSOR_DHT1122_JITTER)) && time <= ((50 + 28) * (1 + SENSOR_DHT1122_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
sensor_dht1122_bits[sensor_dht1122_bit / 8] &= ~(1 << (7 - (sensor_dht1122_bit % 8))); // clear bit
} else if (time >= ((50 + 70)*(1 - SENSOR_DHT1122_JITTER)) && time <= ((50 + 70) * (1 + SENSOR_DHT1122_JITTER))) { // bit 1 time should be 50 us low and 70 us high
sensor_dht1122_bits[sensor_dht1122_bit / 8] |= (1 << (7 - (sensor_dht1122_bit % 8))); // set bit
} else {
goto error;
}
sensor_dht1122_bit++;
if (sensor_dht1122_bit >= 40) { // all bits received
sensor_dht1122_reset(); // reset states
sensor_dht1122_bit = 40; // signal decoder all bits have been received
sensor_dht1122_measurement_received = true; // signal user all bits have been received
}
break;
default: // unexpected state
error:
sensor_dht1122_reset(); // reset states
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

View File

@ -1,30 +0,0 @@
/** library to query measurements from Aosong DHT11 and DHT22/AM2302 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2021
* @note peripherals used: timer channel @ref sensor_dht1122_timer (add external pull-up resistor)
*/
#pragma once
/** a measurement response has been received */
extern volatile bool sensor_dht1122_measurement_received;
/** measurement returned by sensor */
struct sensor_dht1122_measurement_t {
float humidity; /**< relative humidity in %RH (20-95) */
float temperature; /**< temperature in °C (0-50) */
};
/** setup peripherals to communicate with sensor
* @param[in] dht22 false for DHT11, true for DHT22/AM2302
*/
void sensor_dht1122_setup(bool dht22);
/** request measurement from sensor
* @return request started successfully
*/
bool sensor_dht1122_measurement_request(void);
/** decode received measurement
* @return decoded measurement (0xff,0xff if invalid)
*/
struct sensor_dht1122_measurement_t sensor_dht1122_measurement_decode(void);

181
lib/sensor_dht22.c Normal file
View File

@ -0,0 +1,181 @@
/** library to query measurements from Aosong DHT22 temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: GPIO and timer @ref sensor_dht22_timer
* @note the DHT22 protocol is very similar but nit completely compatible with the DHT22 protocol: only 1 ms initial host pull low is required (vs. 18 ms), the data is encoded as int16_t (vs. uint8_t), and the signal has more jitter
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <math.h> // maths utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "sensor_dht22.h" // PZEM electricity meter header and definitions
#include "global.h" // common methods
/** @defgroup sensor_dht22_timer timer peripheral used to measure signal timing for bit decoding
* @{
*/
#define SENSOR_DHT22_TIMER 4 /**< timer peripheral */
#define SENSOR_DHT22_CHANNEL 3 /**< channel used as input capture */
#define SENSOR_DHT22_JITTER 0.2 /**< signal timing jitter tolerated in timing */
/** @} */
volatile bool sensor_dht22_measurement_received = false;
/** communication states */
volatile enum sensor_dht22_state_t {
SENSOR_DHT22_OFF, // no request has started
SENSOR_DHT22_HOST_START, // host starts request (and waits >18ms)
SENSOR_DHT22_HOST_STARTED, // host started request and waits for slave answer
SENSOR_DHT22_SLAVE_START, // slave responds to request and puts signal low for 80 us and high for 80 us
SENSOR_DHT22_SLAVE_BIT, // slave is sending bit by putting signal low for 50 us and high (26-28 us = 0, 70 us = 1)
SENSOR_DHT22_MAX
} sensor_dht22_state = SENSOR_DHT22_OFF; /**< current communication state */
/** the bit number being sent (MSb first), up to 40 */
volatile uint8_t sensor_dht22_bit = 0;
/** the 40 bits (5 bytes) being sent by the device */
volatile uint8_t sensor_dht22_bits[5] = {0};
/** reset all states */
static void sensor_dht22_reset(void)
{
// reset states
sensor_dht22_state = SENSOR_DHT22_OFF;
sensor_dht22_bit = 0;
sensor_dht22_measurement_received = false;
gpio_set(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // idle is high (using pull-up resistor), pull-up before setting as output else the signal will be low for short
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // setup GPIO pin as output (host starts communication before slave replies)
timer_ic_disable(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL)); // enable capture interrupt only when receiving data
timer_disable_counter(TIM(SENSOR_DHT22_TIMER)); // disable timer
}
void sensor_dht22_setup(void)
{
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
rcc_periph_clock_enable(RCC_TIM_CH(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // enable clock for GPIO peripheral
rcc_periph_clock_enable(RCC_TIM(SENSOR_DHT22_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(SENSOR_DHT22_TIMER)); // reset timer state
timer_set_mode(TIM(SENSOR_DHT22_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
timer_set_prescaler(TIM(SENSOR_DHT22_TIMER), 2 - 1); // set the prescaler so this 16 bits timer allows to wait for 18 ms for the start signal ( 1/(72E6/2/(2**16))=1.820ms )
timer_ic_set_input(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_IN_TI(SENSOR_DHT22_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_OFF); // use no filter input (precise timing needed)
timer_ic_set_polarity(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_FALLING); // capture on rising edge
timer_ic_set_prescaler(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(SENSOR_DHT22_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(SENSOR_DHT22_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_CCIF(SENSOR_DHT22_CHANNEL)); // clear input compare flag
timer_enable_irq(TIM(SENSOR_DHT22_TIMER), TIM_DIER_CCIE(SENSOR_DHT22_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_DHT22_TIMER)); // catch interrupt in service routine
sensor_dht22_reset(); // reset state
}
bool sensor_dht22_measurement_request(void)
{
if (sensor_dht22_state != SENSOR_DHT22_OFF) { // not the right state to start (wait up until timeout to reset state)
return false;
}
if (gpio_get(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)) == 0) { // signal should be high per default
return false;
}
if (TIM_CR1(TIM(SENSOR_DHT22_TIMER)) & (TIM_CR1_CEN)) { // timer should be off
return false;
}
sensor_dht22_reset(); // reset states
// send start signal (pull low for > 1 ms)
gpio_clear(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // set signal to low
timer_set_counter(TIM(SENSOR_DHT22_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(SENSOR_DHT22_TIMER)); // enable timer to wait for 1.8 ms until overflow
sensor_dht22_state = SENSOR_DHT22_HOST_START; // remember we started sending signal
return true;
}
struct sensor_dht22_measurement_t sensor_dht22_measurement_decode(void)
{
struct sensor_dht22_measurement_t measurement = { NAN, NAN }; // measurement to return
if (sensor_dht22_bit < 40) { // not enough bits received
return measurement;
}
if ((uint8_t)(sensor_dht22_bits[0] + sensor_dht22_bits[1] + sensor_dht22_bits[2] + sensor_dht22_bits[3]) != sensor_dht22_bits[4]) { // error in checksum (not really parity bit, as mentioned in the datasheet)
return measurement;
}
// calculate measured values (stored as uint16_t deci-value)
measurement.humidity = (int16_t)((sensor_dht22_bits[0] << 8) + sensor_dht22_bits[1]) / 10.0;
measurement.temperature = (int16_t)((sensor_dht22_bits[2] << 8) + sensor_dht22_bits[3]) / 10.0;
return measurement;
}
/** interrupt service routine called for timer */
void TIM_ISR(SENSOR_DHT22_TIMER)(void)
{
if (timer_get_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_UIF); // clear flag
if (sensor_dht22_state==SENSOR_DHT22_HOST_START) { // start signal sent
gpio_set_mode(TIM_CH_PORT(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, TIM_CH_PIN(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL)); // switch pin to input (the external pull up with also set the signal high)
sensor_dht22_state = SENSOR_DHT22_HOST_STARTED; // switch to next state
timer_ic_enable(TIM(SENSOR_DHT22_TIMER), TIM_IC(SENSOR_DHT22_CHANNEL)); // enable capture interrupt only when receiving data
} else { // timeout occurred
sensor_dht22_reset(); // reset states
}
} else if (timer_get_flag(TIM(SENSOR_DHT22_TIMER), TIM_SR_CCIF(SENSOR_DHT22_CHANNEL))) { // edge detected on input capture
uint16_t time = TIM_CCR(SENSOR_DHT22_TIMER,SENSOR_DHT22_CHANNEL); // save captured bit timing (this clear also the flag)
timer_set_counter(TIM(SENSOR_DHT22_TIMER), 0); // reset timer counter
time = (time * 1E6) / (rcc_ahb_frequency / (TIM_PSC(TIM(SENSOR_DHT22_TIMER)) + 1)); // calculate time in us
switch (sensor_dht22_state) {
case (SENSOR_DHT22_HOST_STARTED): // the host query data and the slave is responding
sensor_dht22_state = SENSOR_DHT22_SLAVE_START; // set new state
break;
case (SENSOR_DHT22_SLAVE_START): // the slave sent the start signal
if (time >= ((80 + 80) * (1 - SENSOR_DHT22_JITTER)) && time <= ((80 + 80) * (1 + SENSOR_DHT22_JITTER))) { // response time should be 80 us low and 80 us high
sensor_dht22_state = SENSOR_DHT22_SLAVE_BIT; // set new state
} else {
goto error;
}
break;
case (SENSOR_DHT22_SLAVE_BIT): // the slave sent a bit
if (sensor_dht22_bit >= 40) { // no bits should be received after 40 bits
goto error;
}
if (time >= ((50 + 26) * (1 - SENSOR_DHT22_JITTER)) && time <= ((50 + 28) * (1 + SENSOR_DHT22_JITTER))) { // bit 0 time should be 50 us low and 26-28 us high
sensor_dht22_bits[sensor_dht22_bit / 8] &= ~(1 << (7 - (sensor_dht22_bit % 8))); // clear bit
} else if (time >= ((50 + 70) * (1 - SENSOR_DHT22_JITTER)) && time <= ((50 + 70) * (1 + SENSOR_DHT22_JITTER))) { // bit 1 time should be 50 us low and 70 us high
sensor_dht22_bits[sensor_dht22_bit / 8] |= (1 << (7 - (sensor_dht22_bit % 8))); // set bit
} else {
goto error;
}
sensor_dht22_bit++;
if (sensor_dht22_bit >= 40) { // all bits received
sensor_dht22_reset(); // reset states
sensor_dht22_bit = 40; // signal decoder all bits have been received
sensor_dht22_measurement_received = true; // signal user all bits have been received
}
break;
default: // unexpected state
error:
sensor_dht22_reset(); // reset states
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

28
lib/sensor_dht22.h Normal file
View File

@ -0,0 +1,28 @@
/** library to query measurements from Aosong DHT22 (aka. AM2302) temperature and relative humidity sensor
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2017-2020
* @note peripherals used: timer channel @ref sensor_dht22_timer (add external pull-up resistor)
*/
#pragma once
/** a measurement response has been received */
extern volatile bool sensor_dht22_measurement_received;
/** measurement returned by sensor */
struct sensor_dht22_measurement_t {
float humidity; /**< relative humidity in %RH (0-100) */
float temperature; /**< temperature in °C (-40-80) */
};
/** setup peripherals to communicate with sensor */
void sensor_dht22_setup(void);
/** request measurement from sensor
* @return request started successfully
*/
bool sensor_dht22_measurement_request(void);
/** decode received measurement
* @return decoded measurement (0xff,0xff if invalid)
*/
struct sensor_dht22_measurement_t sensor_dht22_measurement_decode(void);

View File

@ -1,75 +0,0 @@
/** library to communication with Maxim MAX6675 K-type thermocouple to digital temperature sensor using SPI
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2020
* @note peripherals used: SPI @ref sensor_max6675_spi
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <stdbool.h> // boolean utilities
#include <math.h> // math utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/spi.h> // SPI library
/* own library */
#include "global.h" // common definitions
#include "sensor_max6675.h" // own definitions
/** @defgroup sensor_max6675_spi SPI peripheral used to communicate with the AS3935
* @note SCK, MISO, and NSS pins are used
*/
#define SENSOR_MAX6675_SPI 1 /**< SPI peripheral */
void sensor_max6675_setup(void)
{
// setup SPI
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(SENSOR_MAX6675_SPI)); // enable clock for GPIO peripheral for clock signal
gpio_set_mode(SPI_SCK_PORT(SENSOR_MAX6675_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(SENSOR_MAX6675_SPI)); // set SCK as output (clock speed will be negotiated later)
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(SENSOR_MAX6675_SPI)); // enable clock for GPIO peripheral for MISO signal
gpio_set_mode(SPI_MISO_PORT(SENSOR_MAX6675_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_MISO_PIN(SENSOR_MAX6675_SPI)); // set MISO as input
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(SENSOR_MAX6675_SPI)); // enable clock for GPIO peripheral for NSS (CS) signal
gpio_set_mode(SPI_NSS_PORT(SENSOR_MAX6675_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, SPI_NSS_PIN(SENSOR_MAX6675_SPI)); // set NSS (CS) as output
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(RCC_SPI(SENSOR_MAX6675_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(SENSOR_MAX6675_SPI)); // clear SPI values to default
spi_init_master(SPI(SENSOR_MAX6675_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_32, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_2, SPI_CR1_DFF_16BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 64 (72E6/32=2250 kHz, max AS3935 SCK is 4.3, maximum SPI PCLK clock is 72 Mhz, depending on which SPI is used), set clock polarity to idle low (not that important), set clock phase to do bit change on falling edge (polarity depends on clock phase), use 16 bits frames , use MSb first
spi_set_unidirectional_mode(SPI(SENSOR_MAX6675_SPI)); // set simplex mode (only two wires used)
// do not set as receive only to trigger transfer (read) using write
//spi_set_receive_only_mode(SPI(SENSOR_MAX6675_SPI)); // we will only receive data
spi_enable_software_slave_management(SPI(SENSOR_MAX6675_SPI)); // control NSS (CS) manually
spi_set_nss_high(SPI(SENSOR_MAX6675_SPI)); // set NSS high (internally) so we can get input
spi_disable_ss_output(SPI(SENSOR_MAX6675_SPI)); // disable NSS output since we control CS manually
gpio_set(SPI_NSS_PORT(SENSOR_MAX6675_SPI), SPI_NSS_PIN(SENSOR_MAX6675_SPI)); // set CS high to unselect device
spi_enable(SPI(SENSOR_MAX6675_SPI)); // enable SPI
}
void sensor_max6675_release(void)
{
spi_reset(SPI(SENSOR_MAX6675_SPI));
spi_disable(SPI(SENSOR_MAX6675_SPI));
rcc_periph_clock_disable(RCC_SPI(SENSOR_MAX6675_SPI));
gpio_set_mode(SPI_NSS_PORT(SENSOR_MAX6675_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_NSS_PIN(SENSOR_MAX6675_SPI));
gpio_set_mode(SPI_MISO_PORT(SENSOR_MAX6675_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_MISO_PIN(SENSOR_MAX6675_SPI));
gpio_set_mode(SPI_SCK_PORT(SENSOR_MAX6675_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_SCK_PIN(SENSOR_MAX6675_SPI));
}
float sensor_max6675_read(void)
{
(void)SPI_DR(SPI(SENSOR_MAX6675_SPI)); // clear RXNE flag (by reading previously received data (not done by spi_read or spi_xref)
gpio_clear(SPI_NSS_PORT(SENSOR_MAX6675_SPI), SPI_NSS_PIN(SENSOR_MAX6675_SPI)); // set CS low to select device
const uint16_t temp = spi_xfer(SPI(SENSOR_MAX6675_SPI), 0); // read data
gpio_set(SPI_NSS_PORT(SENSOR_MAX6675_SPI), SPI_NSS_PIN(SENSOR_MAX6675_SPI)); // set CS high to unselect device
if (temp & 0x8002) { // sign and device ID bits should not be set
return NAN;
}
if (temp & 0x0004) { // thermocouple is open
return INFINITY;
}
return (temp >> 3) / 4.0; // return temperature value
}

View File

@ -1,19 +0,0 @@
/** library to communication with Maxim MAX6675 K-type thermocouple to digital temperature sensor using SPI
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2020
* @note peripherals used: SPI @ref sensor_max6675_spi
*/
#pragma once
/** setup communication to MAX6675 sensor */
void sensor_max6675_setup(void);
/** release peripherals used to communicate with MAX6675 sensor */
void sensor_max6675_release(void);
/** read temperature from MAX6675 sensor
* @return temperature (in °C) measured by sensor (infinity if K-thermocouple is missing, nan on error)
* @note resolution is in 0.25 °C
* @note wait 0.22 s between readings (max. time needed for a conversion)
*/
float sensor_max6675_read(void);

View File

@ -34,7 +34,7 @@ static usbd_device *usb_device = NULL; /**< structure holding all the info relat
static volatile bool first_connection = false; /**< used to detect when the first connection occurred */
/* output ring buffer, index, and available memory */
static uint8_t tx_buffer[256] = {0}; /**< ring buffer for data to transmit */
static uint8_t tx_buffer[1024] = {0}; /**< ring buffer for data to transmit */
static volatile uint16_t tx_i = 0; /**< current position if transmitted data */
static volatile uint16_t tx_used = 0; /**< how much data needs to be transmitted */
static volatile bool tx_lock = false; /**< if the transmit buffer is currently being written */

View File

@ -234,7 +234,7 @@ static enum usbd_request_return_codes usb_dfu_control_request(usbd_device *usbd_
// application data is exceeding enforced flash size for application
usb_dfu_status = DFU_STATUS_ERR_ADDRESS;
usb_dfu_state = STATE_DFU_ERROR;
} else if ((uint32_t)&__application_end < FLASH_BASE && (uint32_t)&__application_beginning + download_offset + download_length >= (uint32_t)(FLASH_BASE + desig_get_flash_size() * 1024)) {
} else if ((uint32_t)&__application_end < FLASH_BASE && (uint32_t)&__application_beginning + download_offset + download_length >= (uint32_t)(FLASH_BASE + DESIG_FLASH_SIZE * 1024)) {
// application data is exceeding advertised flash size
usb_dfu_status = DFU_STATUS_ERR_ADDRESS;
usb_dfu_state = STATE_DFU_ERROR;

@ -1 +1 @@
Subproject commit 664701d7a7f169235c457a3dd117415647aac61b
Subproject commit cb0661f81de5b1cae52ca99c7b5985b678176db7