Last active
August 19, 2020 08:53
-
-
Save tomtor/99e2a33bfa404ff99454aa94fc0df804 to your computer and use it in GitHub Desktop.
ESP32: ULP LED Hart beat and read DHT22 sensor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Blink ULP Hartbeat and read DHT22 in ULP code | |
Tom Vijlbrief oct 2017 | |
*/ | |
#include <time.h> | |
#include <sys/time.h> | |
#include "driver/rtc_io.h" | |
#include <driver/adc.h> | |
#include <esp_adc_cal.h> | |
#include "esp32/ulp.h" | |
#include "soc/rtc_cntl_reg.h" | |
#include "soc/sens_reg.h" | |
#include "soc/rtc.h" | |
#include "esp_sleep.h" | |
#include "esp_err.h" | |
#include <WiFi.h> | |
#include <WiFiUdp.h> | |
#include "wifi-pw.h" // My network and password | |
// WiFi network name and password: | |
const char * networkName = NETWORK; | |
const char * networkPswd = PASSWORD; | |
const gpio_num_t gpio_led = GPIO_NUM_2; | |
const gpio_num_t gpio_dht = GPIO_NUM_32; | |
const gpio_num_t gpio_builtin = GPIO_NUM_22; | |
const int V_REF= 1117; | |
//IP address to send UDP data to: | |
// either use the ip address of the server or | |
// a network broadcast address | |
const char * udpAddress = "192.168.0.9"; | |
const int udpPort = 41234; | |
//Are we currently connected? | |
boolean connected = false; | |
//The udp library class | |
WiFiUDP udp; | |
uint64_t chipid; | |
RTC_DATA_ATTR int bootCount = 0; | |
/* | |
Offset (in 32-bit words) in RTC Slow memory where the data is placed | |
by the ULP coprocessor. It can be chosen to be any value greater or equal | |
to ULP program size, and less than the CONFIG_ULP_COPROC_RESERVE_MEM/4 - 6, | |
where 6 is the number of words used by the ULP coprocessor. | |
*/ | |
#define ULP_DATA_OFFSET 200 | |
#define FLASH_CNT 0 | |
#define DATA_CNT 1 | |
#define ADC 2 | |
#define TEMP_OFFSET 100 | |
#define RH_OFFSET 200 | |
/** | |
@brief Utility function which reads data written by ULP program | |
@param offset offset from ULP_DATA_OFFSET in RTC Slow memory, in words | |
@return lower 16-bit part of the word writable by the ULP | |
*/ | |
static inline uint16_t ulp_data_read(size_t offset) | |
{ | |
return RTC_SLOW_MEM[ULP_DATA_OFFSET + offset] & 0xffff; | |
} | |
/** | |
@brief Utility function which writes data to be ready by ULP program | |
@param offset offset from ULP_DATA_OFFSET in RTC Slow memory, in words | |
@param value lower 16-bit part of the word to be stored | |
*/ | |
static inline void ulp_data_write(size_t offset, uint16_t value) | |
{ | |
RTC_SLOW_MEM[ULP_DATA_OFFSET + offset] = value; | |
} | |
void connectToWiFi(const char * ssid, const char * pwd){ | |
Serial.println("Connecting to WiFi network: " + String(ssid)); | |
// delete old config | |
WiFi.disconnect(true); | |
//register event handler | |
WiFi.onEvent(WiFiEvent); | |
//Initiate connection | |
WiFi.begin(ssid, pwd); | |
Serial.println("Waiting for WIFI connection..."); | |
} | |
//wifi event handler | |
void WiFiEvent(WiFiEvent_t event){ | |
switch(event) { | |
case SYSTEM_EVENT_STA_GOT_IP: | |
//When connected set | |
Serial.print("WiFi connected! IP address: "); | |
Serial.println(WiFi.localIP()); | |
//initializes the UDP state | |
//This initializes the transfer buffer | |
udp.begin(WiFi.localIP(),udpPort); | |
connected = true; | |
break; | |
case SYSTEM_EVENT_STA_DISCONNECTED: | |
Serial.println("WiFi lost connection"); | |
connected = false; | |
break; | |
default: | |
break; | |
} | |
} | |
void setup() { | |
pinMode(gpio_builtin, OUTPUT); | |
digitalWrite(gpio_builtin, LOW); | |
Serial.begin(115200); | |
chipid= ESP.getEfuseMac(); // The chip ID is essentially its MAC address(length: 6 bytes) | |
if (bootCount) return; | |
#if 0 | |
Serial.println("Measure VREF at GPIO 25"); | |
esp_err_t status = adc2_vref_to_gpio(GPIO_NUM_25); | |
if (status != ESP_OK) { | |
Serial.println("failed to route v_ref\n"); | |
} | |
delay(10000); | |
#endif | |
if (!rtc_gpio_is_valid_gpio(gpio_led) || !rtc_gpio_is_valid_gpio(gpio_dht)) | |
Serial.println("Bad IO"); | |
rtc_gpio_init(gpio_led); | |
rtc_gpio_set_direction(gpio_led, RTC_GPIO_MODE_OUTPUT_ONLY); | |
//rtc_gpio_set_drive_capability(gpio_led, GPIO_DRIVE_CAP_DEFAULT); | |
//rtc_gpio_hold_dis(gpio_num); | |
rtc_gpio_init(gpio_dht); | |
rtc_gpio_set_direction(gpio_dht, RTC_GPIO_MODE_INPUT_ONLY); | |
//rtc_gpio_pullup_en(gpio_dht); | |
#if 1 | |
ulp_data_write(FLASH_CNT, 0); | |
ulp_data_write(DATA_CNT, 0); | |
const ulp_insn_t program[] = { | |
// load data offset into R2 | |
I_MOVI(R2, ULP_DATA_OFFSET), | |
I_LD(R1, R2, FLASH_CNT), // increment counter | |
I_ADDI(R1, R1, 1), | |
I_ST(R1, R2, FLASH_CNT), | |
#if 1 | |
I_ANDI(R0, R1, 0x3FFF), | |
M_BXZ(6), // Read sensor data | |
#endif | |
M_LABEL(0), | |
I_ANDI(R1, R1, 0xFFFF), // Wakeup? | |
M_BXZ(5), | |
I_ANDI(R1, R1, 0x3FF), | |
M_BXZ(1), // long interval between 2 hartbeats | |
I_SUBI(R3, R1, 1), | |
M_BXZ(3), // first beat | |
I_SUBI(R3, R1, 2), | |
M_BXZ(4), // dark between first and second beat | |
I_SUBI(R3, R1, 40), // second beat | |
M_BXZ(3), | |
M_LABEL(1), | |
// I_SLEEP_CYCLE_SEL(0), // 2s | |
M_LABEL(2), | |
I_WR_REG_BIT(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_HOLD_S, 0), // HOLD off | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 0), // RTC_GPIO_12 == GPIO_02, LED off | |
I_WR_REG_BIT(RTC_GPIO_OUT_W1TC_REG,RTC_GPIO_OUT_DATA_W1TC_S+12,1), | |
I_WR_REG_BIT(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_HOLD_S, 1), // HOLD on | |
I_HALT(), | |
M_LABEL(3), // turn LED on | |
I_WR_REG_BIT(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_HOLD_S, 0), // HOLD off | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 1), | |
I_WR_REG_BIT(RTC_GPIO_OUT_W1TS_REG,RTC_GPIO_OUT_DATA_W1TS_S+12,1), | |
I_WR_REG_BIT(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_HOLD_S, 1), // HOLD on | |
//I_SLEEP_CYCLE_SEL(1), // 10ms | |
// stop the ULP program | |
I_HALT(), | |
M_LABEL(4), | |
//I_SLEEP_CYCLE_SEL(2), // 100ms | |
M_BX(2), // led OFF | |
// Wakeup | |
M_LABEL(5), | |
I_END(), | |
I_WAKE(), | |
I_HALT(), | |
M_LABEL(6), | |
#if 1 | |
#if 1 // Read DHT22 | |
// Pull low for 1.5ms | |
I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 9, 0), | |
// Output mode | |
I_WR_REG_BIT(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + 9, 1), // RTC_GPIO_9 == GPIO_32 | |
I_MOVI(R0, 0), | |
M_LABEL(7), | |
I_ADDI(R0,R0,1), | |
M_BL(7,1500), | |
// Input mode: | |
I_WR_REG_BIT(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + 9, 1), // RTC_GPIO_9 == GPIO_32 | |
// Wait for sensor pull down, with time out | |
I_MOVI(R3,100), | |
M_LABEL(8), | |
I_SUBI(R3,R3,1), | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 1), I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 0), | |
M_BXZ(0), // Time out | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
M_BGE(8,1), | |
// Wait for sensor pull up | |
M_LABEL(9), | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 1), I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 0), | |
M_BL(9,1), | |
// Start of data loop | |
I_MOVI(R1,16), // read 16 bits | |
I_MOVI(R2,0), // shift data bits into R2 | |
// Wait for sensor pull down | |
M_LABEL(10), | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 1), I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 0), | |
M_BGE(10,1), | |
// Wait for sensor pull up | |
M_LABEL(11), | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 1), I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 0), | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
M_BL(11,1), | |
// Now measure time to next pull down | |
I_MOVI(R3,0), | |
M_LABEL(12), | |
I_ADDI(R3,R3,1), | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
M_BGE(12,1), | |
// 40 loops is a 1 bit, 14 loops is a 0 bit, update current slot | |
I_LSHI(R2, R2, 1), | |
I_RSHI(R3,R3,5), // 32 or more loops | |
I_ORR(R2,R2,R3), | |
I_SUBI(R1,R1,1), | |
I_MOVR(R0,R1), | |
M_BGE(10,1), // Get next bit | |
I_MOVR(R3, R2), // Save result in R3 | |
I_MOVI(R2, ULP_DATA_OFFSET), // restore R2 | |
I_LD(R0, R2, DATA_CNT), // load measurement counter | |
// Save current measurement | |
I_ADDR(R0, R0, R2), | |
I_ST(R3, R0, RH_OFFSET), | |
// Now repeat for temperature data: | |
// Start of data loop | |
I_MOVI(R1,16), // read 16 bits | |
I_MOVI(R2,0), // shift data bits into R2 | |
// Wait for sensor pull down | |
M_LABEL(20), | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 1), I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 0), | |
M_BGE(20,1), | |
// Wait for sensor pull up | |
M_LABEL(21), | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
//I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 1), I_WR_REG_BIT(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + 12, 0), | |
M_BL(21,1), | |
// Now measure time to next pull down | |
I_MOVI(R3,0), | |
M_LABEL(22), | |
I_ADDI(R3,R3,1), | |
I_RD_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 9, RTC_GPIO_IN_NEXT_S + 9), | |
M_BGE(22,1), | |
// 40 loops is a 1 bit, 14 loops is a 0 bit, update current slot | |
I_LSHI(R2, R2, 1), | |
I_RSHI(R3,R3,5), // 32 or more loops | |
I_ORR(R2,R2,R3), | |
I_SUBI(R1,R1,1), | |
I_MOVR(R0,R1), | |
M_BGE(20,1), // Get next bit | |
I_MOVR(R3, R2), // Save result in R3 | |
I_MOVI(R2, ULP_DATA_OFFSET), // restore R2 | |
I_LD(R0, R2, DATA_CNT), // increment measurement counter | |
I_ADDI(R0, R0, 1), | |
I_ST(R0, R2, DATA_CNT), | |
// Save current measurement | |
I_ADDR(R0, R0, R2), | |
I_ST(R3, R0, TEMP_OFFSET-1), | |
// Resume main hartbeat loop | |
I_LD(R1, R2, FLASH_CNT), // Restore counter to R1 | |
#else // Read internal sensor | |
I_LD(R0, R2, DATA_CNT), // increment counter | |
I_ADDI(R0, R0, 1), | |
I_ST(R0, R2, DATA_CNT), | |
// enable temperature sensor | |
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 3), | |
// do temperature measurement and store result in R3 | |
I_TSENS(R3, 8000), | |
// disable temperature sensor | |
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 0), | |
// Save current measurement | |
I_ADDR(R0, R0, R2), | |
I_ST(R3, R0, TEMP_OFFSET-1), | |
#endif | |
#endif | |
M_BX(0), | |
}; | |
// Load ULP program into RTC_SLOW_MEM, at offset 0 | |
size_t size = sizeof(program) / sizeof(ulp_insn_t); | |
ESP_ERROR_CHECK( ulp_process_macros_and_load(0, program, &size) ); | |
assert(size < ULP_DATA_OFFSET && "ULP_DATA_OFFSET needs to be greater or equal to the program size"); | |
// Set ULP wakeup periods | |
REG_WRITE(SENS_ULP_CP_SLEEP_CYC0_REG, rtc_clk_slow_freq_get_hz() / 180); // About 5ms | |
// REG_WRITE(SENS_ULP_CP_SLEEP_CYC1_REG, rtc_clk_slow_freq_get_hz() / 180); // About 5ms LED on | |
// REG_WRITE(SENS_ULP_CP_SLEEP_CYC2_REG, rtc_clk_slow_freq_get_hz() / 10); // About 100ms hartbeat interval | |
#endif | |
} | |
void loop() | |
{ | |
Serial.print("Boot: "); Serial.println(bootCount); | |
Serial.print("Flash: "); Serial.println(ulp_data_read(FLASH_CNT)); | |
int cnt= ulp_data_read(DATA_CNT); | |
Serial.print("Data: "); Serial.println(cnt); | |
// for (int i= 0; i < cnt; ++i) { | |
// Serial.print(ulp_data_read(RH_OFFSET+i)); Serial.print(' '); | |
// Serial.println(ulp_data_read(TEMP_OFFSET+i)); | |
// } | |
ulp_data_write(DATA_CNT, 0); | |
// for (int i= 0; i < 8; i++) { | |
// Serial.print("adc: "); Serial.println(ulp_data_read(ADC+i)); | |
// } | |
if (true || (bootCount & 0x3) == 0) { | |
//Connect to the WiFi network | |
connectToWiFi(networkName, networkPswd); | |
adc1_config_width(ADC_WIDTH_12Bit); | |
adc1_config_channel_atten(ADC1_CHANNEL_6,ADC_ATTEN_11db); | |
// Calculate ADC characteristics i.e. gain and offset factors | |
esp_adc_cal_characteristics_t characteristics; | |
esp_adc_cal_get_characteristics(V_REF, ADC_ATTEN_11db, ADC_WIDTH_12Bit, &characteristics); | |
// Read ADC and obtain result in mV | |
uint32_t milliVolt = adc1_to_voltage(ADC1_CHANNEL_6, &characteristics); | |
//int milliVolt = adc1_get_raw(ADC1_CHANNEL_6) * int(1100 * 3.6) / 4095; | |
Serial.print("milliVolt: "); Serial.println(milliVolt); | |
//only send data when connected | |
while (!connected) { | |
Serial.print("."); | |
delay(100); | |
} | |
String str; | |
for (int i= 0; i < cnt; ++i) { | |
str+= ulp_data_read(TEMP_OFFSET+i); str+= ' '; | |
str+= ulp_data_read(RH_OFFSET+i); str+= ' '; | |
} | |
Serial.println(str); | |
RTC_DATA_ATTR static time_t prev, now; | |
time(&now); | |
time_t diff= now-prev; | |
prev= now; | |
Serial.println(diff); | |
//Send a packet | |
// udp.beginPacket(udpAddress,udpPort); | |
// udp.printf("%u %u %u %u %u %lu %s", (uint32_t)chipid, | |
// !cnt ? 210 : ulp_data_read(TEMP_OFFSET+cnt-1), | |
// milliVolt, | |
// !cnt ? 500 : ulp_data_read(RH_OFFSET+cnt-1), | |
// cnt, diff, str.c_str()); | |
// | |
// udp.endPacket(); | |
delay(10); | |
} | |
++bootCount; | |
time_t now; | |
struct tm timeinfo; | |
time(&now); | |
localtime_r(&now, &timeinfo); | |
char strftime_buf[64]; | |
setenv("TZ", "UTC", 1); | |
tzset(); | |
localtime_r(&now, &timeinfo); | |
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); | |
Serial.print("The current date/time is: "); | |
Serial.println(strftime_buf); | |
//if (rtc_gpio_set_level(gpio_dht, bootCount & 1) != ESP_OK) | |
// Serial.println("Error!"); | |
//rtc_gpio_hold_en(gpio_led); | |
//esp_sleep_enable_timer_wakeup(10 * 1000000); | |
//esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); | |
// Start ULP | |
//if (bootCount == 1) | |
ESP_ERROR_CHECK( ulp_run(0) ); | |
esp_sleep_enable_ulp_wakeup(); | |
esp_deep_sleep_start(); | |
} |
hi @gdampf, do you mean that you kept the ULP running in sleep/wake cycles forever without restarting it? that would work for me.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@mahesh2000, following the description in the link from my last post, I got it working without WiFi by disabling the I_END(), but this may not be satisfying in all cases. I decided, to do it without ULP, but deep sleep between the measures, so I managed to have my Sensor sending in 10 min. intervals powered by a small rechargeable battery for about 2 months, which is fine. But, of course, I get only 6 samples per hour this way. For temperature and humidity, this nevertheless is exact enough in most cases.