Compare commits

...

4 commits

Author SHA1 Message Date
Patrick Moessler
fc2f57e4cb crude first version of main part 2024-01-06 22:04:07 +01:00
Patrick Moessler
a5cd63a264 increase i2c speed 2024-01-06 22:03:44 +01:00
Patrick Moessler
c7d9cb3fa8 add shield to add driver to hardware setup 2024-01-06 22:03:37 +01:00
Patrick Moessler
2c53c45c4b add mlx90640 driver 2024-01-06 22:03:13 +01:00
19 changed files with 2312 additions and 10 deletions

View file

@ -1,2 +1,4 @@
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
add_subdirectory(drivers)

View file

@ -5,5 +5,5 @@
# as the module Kconfig entry point (see zephyr/module.yml). You can browse
# module options by going to Zephyr -> Modules in Kconfig.
# rsource "drivers/Kconfig"
rsource "drivers/Kconfig"
# rsource "lib/Kconfig"

View file

@ -10,6 +10,9 @@ CONFIG_I2C_SHELL=y
CONFIG_INPUT_SHELL=y
CONFIG_LV_Z_SHELL=y
CONFIG_SENSOR_SHELL=y
# CONFIG_SENSOR_SHELL_BATTERY=y
# LVGL
CONFIG_LVGL=y
CONFIG_LV_COLOR_DEPTH_32=y

View file

@ -12,11 +12,14 @@
#include <lvgl_input_device.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/input/input.h>
#include <zephyr/random/random.h>
#include <zephyr/logging/log.h>
#include "../../drivers/sensor/mlx90640/mlx90640.h"
LOG_MODULE_REGISTER(main, CONFIG_APP_LOG_LEVEL);
int main(void)
@ -26,6 +29,7 @@ int main(void)
char count_str[11] = {0};
const struct device *display_dev;
const struct device *ir_dev;
display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(display_dev))
@ -47,33 +51,64 @@ int main(void)
lv_obj_t *ir_image = lv_canvas_create(lv_scr_act());
lv_canvas_set_buffer(ir_image, cbuf, 32, 24, LV_IMG_CF_TRUE_COLOR);
lv_canvas_fill_bg(ir_image, lv_color_make(0x40,0x40,0x40), LV_OPA_COVER);
lv_canvas_fill_bg(ir_image, lv_color_make(0x40, 0x40, 0x40), LV_OPA_COVER);
lv_obj_set_size(ir_image, 32, 24);
lv_obj_align(ir_image, LV_ALIGN_TOP_LEFT, 4, 4);
lv_img_set_pivot(ir_image, 0, 0);
lv_img_set_zoom(ir_image, 256 * 8);
// lv_img_set_zoom(ir_image, 256 * 2);
lv_img_set_zoom(ir_image, 256 * 12);
lv_img_set_antialias(ir_image, false);
lv_timer_handler();
display_blanking_off(display_dev);
ir_dev = DEVICE_DT_GET(DT_NODELABEL(mlx90640_mlx90640_elecrow_i2c));
if (!device_is_ready(ir_dev))
{
printf("Device %s not ready\n", ir_dev->name);
return 0;
}
lv_color32_t color;
float *temps = ((struct mlx90640_data *)ir_dev->data)->temps;
int64_t reftime = k_uptime_get();
while (1)
{
sensor_sample_fetch(ir_dev);
mlx90640_calculate_to(ir_dev, 0.8, ((struct mlx90640_data *)ir_dev->data)->ta - 8.0f); // Tr = Ta - 8.0 from PDF
for (int y = 0; y < 24; y++)
{
for (int x = 0; x < 32; x++)
{
color.ch.red = (color.ch.red + 11) % 255;
color.ch.green = (color.ch.green + 17) % 255;
color.ch.blue = (color.ch.blue + 13) % 255;
if (temps[y * 32 + x] < 0)
{
color = lv_color_black();
}
else if (temps[y * 32 + x] > 64)
{
color = lv_color_white();
}
else
{
color.ch.red = (uint8_t)(temps[y * 32 + x] * 4);
color.ch.green = color.ch.red;
color.ch.blue = color.ch.red;
}
lv_canvas_set_px_color(ir_image, x, y, color);
}
}
int delay = lv_timer_handler_run_in_period(100);
k_msleep(delay);
lv_timer_handler();
int64_t elapsed = k_uptime_delta(&reftime);
printf("took %d ms", (uint32_t)elapsed);
if (elapsed < 100)
{
k_msleep(100 - elapsed);
}
}
return 0;

View file

@ -0,0 +1,12 @@
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
if SHIELD_MLX90640_ELECROW_I2C
config SENSOR
default y
config MLX90640
default y
endif # SHIELD_MLX90640_ELECROW_I2C

View file

@ -0,0 +1,6 @@
# Copyright (c) 2024 PM
#
# SPDX-License-Identifier: Apache-2.0
config SHIELD_MLX90640_ELECROW_I2C
def_bool $(shields_list_contains,mlx90640_elecrow_i2c)

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2024 PM
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c0 {
status = "okay";
mlx90640_mlx90640_elecrow_i2c: mlx90640@33 {
compatible = "melexis,mlx90640";
reg = <0x33>;
status = "okay";
};
};

View file

@ -82,7 +82,7 @@
};
&i2c0 {
clock-frequency = <I2C_BITRATE_STANDARD>;
clock-frequency = <I2C_BITRATE_FAST>;
pinctrl-0 = <&i2c0_default>;
pinctrl-names = "default";
status = "okay";

View file

@ -4,7 +4,8 @@
CONFIG_BOARD_ELECROW_ESP_TERMINAL=y
CONFIG_SOC_SERIES_ESP32S3=y
CONFIG_MAIN_STACK_SIZE=8192 # increased for LVGL
# increased for LVGL
CONFIG_MAIN_STACK_SIZE=8192
CONFIG_CONSOLE=y
CONFIG_SERIAL=y

4
drivers/CMakeLists.txt Normal file
View file

@ -0,0 +1,4 @@
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
add_subdirectory_ifdef(CONFIG_SENSOR sensor)

6
drivers/Kconfig Normal file
View file

@ -0,0 +1,6 @@
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
menu "Drivers"
rsource "sensor/Kconfig"
endmenu

View file

@ -0,0 +1,4 @@
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
add_subdirectory_ifdef(CONFIG_MLX90640 mlx90640)

6
drivers/sensor/Kconfig Normal file
View file

@ -0,0 +1,6 @@
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
if SENSOR
rsource "mlx90640/Kconfig"
endif # SENSOR

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(mlx90640.c)
# zephyr_library_sources_ifdef(CONFIG_MLX90640_TRIGGER mlx90640_trigger.c)
# zephyr_library_sources(vendor/functions/MLX90640.c)

View file

@ -0,0 +1,55 @@
# MLX90640 infrared thermopile sensor configuration options
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
# menuconfig MLX90640
config MLX90640
bool "MLX90640 Infrared Thermopile Sensor"
default y
depends on DT_HAS_MELEXIS_MLX90640_ENABLED
select I2C
help
Enable driver for MLX90640 infrared thermopile sensor.
# if MLX90640
# choice
# prompt "Trigger mode"
# default MLX90640_TRIGGER_NONE
# help
# Specify the type of triggering used by the driver.
# config MLX90640_TRIGGER_NONE
# bool "No trigger"
# config MLX90640_TRIGGER_GLOBAL_THREAD
# bool "Use global thread"
# depends on GPIO
# select MLX90640_TRIGGER
# config MLX90640_TRIGGER_OWN_THREAD
# bool "Use own thread"
# depends on GPIO
# select MLX90640_TRIGGER
# endchoice
# config MLX90640_TRIGGER
# bool
# config MLX90640_THREAD_PRIORITY
# int "Thread priority"
# depends on MLX90640_TRIGGER_OWN_THREAD
# default 10
# help
# Priority of thread used by the driver to handle interrupts.
# config MLX90640_THREAD_STACK_SIZE
# int "Thread stack size"
# depends on MLX90640_TRIGGER_OWN_THREAD
# default 1024
# help
# Stack size of thread used by the driver to handle interrupts.
# endif # MLX90640

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2024 PM
*
* using code from Melexis https://github.com/melexis/mlx90640-library,
* commit f6be7ca1d4a55146b705f3d347f84b773b29cc86 under Apache-2.0
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_MLX90640_MLX90640_H_
#define ZEPHYR_DRIVERS_SENSOR_MLX90640_MLX90640_H_
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/util.h>
typedef struct
{
int16_t kVdd;
int16_t vdd25;
float KvPTAT;
float KtPTAT;
uint16_t vPTAT25;
float alphaPTAT;
int16_t gainEE;
float tgc;
float cpKv;
float cpKta;
uint8_t resolutionEE;
uint8_t calibrationModeEE;
float KsTa;
float ksTo[5];
int16_t ct[5];
uint16_t alpha[768];
uint8_t alphaScale;
int16_t offset[768];
int8_t kta[768];
uint8_t ktaScale;
int8_t kv[768];
uint8_t kvScale;
float cpAlpha[2];
int16_t cpOffset[2];
float ilChessC[3];
uint16_t brokenPixels[5];
uint16_t outlierPixels[5];
} mlx90640_params;
enum mlx90640_refresh_rate
{
MLX90640_REFRESH_0_5 = 0,
MLX90640_REFRESH_1 = 1,
MLX90640_REFRESH_2 = 2,
MLX90640_REFRESH_4 = 3,
MLX90640_REFRESH_8 = 4,
MLX90640_REFRESH_16 = 5,
MLX90640_REFRESH_32 = 6,
MLX90640_REFRESH_64 = 7,
};
enum mlx90640_adc_resolution
{
MLX90640_ADC_RES_16 = 0,
MLX90640_ADC_RES_17 = 1,
MLX90640_ADC_RES_18 = 2,
MLX90640_ADC_RES_19 = 3,
};
enum mlx90640_reading_pattern
{
MLX90640_PATTERN_INTERLEAVED = 0,
MLX90640_PATTERN_CHESS = 1,
};
struct mlx90640_config
{
const struct i2c_dt_spec i2c;
const enum mlx90640_refresh_rate refresh_rate;
const enum mlx90640_adc_resolution adc_resolution;
const enum mlx90640_reading_pattern reading_pattern;
};
struct mlx90640_data
{
float temps[768];
float vdd;
float ta;
uint16_t raw_data[833]; // 768 px + 64 metadata + 1 status
mlx90640_params params;
#ifdef CONFIG_MLX90640_TRIGGER
const struct device *dev;
struct gpio_callback gpio_cb;
sensor_trigger_handler_t drdy_handler;
const struct sensor_trigger *drdy_trigger;
sensor_trigger_handler_t th_handler;
const struct sensor_trigger *th_trigger;
#if defined(CONFIG_MLX90640_TRIGGER_OWN_THREAD)
K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_MLX90640_THREAD_STACK_SIZE);
struct k_sem gpio_sem;
struct k_thread thread;
#elif defined(CONFIG_MLX90640_TRIGGER_GLOBAL_THREAD)
struct k_work work;
#endif
#endif /* CONFIG_MLX90640_TRIGGER */
};
#ifdef CONFIG_MLX90640_TRIGGER
int mlx90640_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr,
const struct sensor_value *val);
int mlx90640_trigger_set(const struct device *dev, const struct sensor_trigger *trig, sensor_trigger_handler_t handler);
int mlx90640_init_interrupt(const struct device *dev);
#endif /* CONFIG_MLX90640_TRIGGER */
// data
int mlx90640_synch_frame(const struct device *dev);
int mlx90640_trigger_measurement(const struct device *dev);
int mlx90640_get_frame_data(const struct device *dev);
// process
float mlx90640_get_vdd(const struct device *dev);
float mlx90640_get_ta(const struct device *dev);
// void mlx90640_get_image(const struct device *dev);
// or!
void mlx90640_calculate_to(const struct device *dev, float emissivity, float tr);
// optional /
void mlx90640_bad_pixels_correction(const struct device *dev, int mode);
int mlx90640_set_adc_resolution(const struct device *dev, enum mlx90640_adc_resolution res);
enum mlx90640_adc_resolution mlx90640_get_adc_resolution(const struct device *dev);
int mlx90640_set_refresh_rate(const struct device *dev, enum mlx90640_refresh_rate res);
enum mlx90640_refresh_rate mlx90640_get_refresh_rate(const struct device *dev);
int mlx90640_set_reading_pattern(const struct device *dev, enum mlx90640_reading_pattern res);
enum mlx90640_reading_pattern mlx90640_get_reading_pattern(const struct device *dev);
#endif

View file

@ -0,0 +1,213 @@
/*
* Copyright (c) 2017-2019 Phytec Messtechnik GmbH
* Copyright (c) 2017 Benedict Ohl (Benedict-Ohl@web.de)
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include "amg88xx.h"
extern struct amg88xx_data amg88xx_driver;
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(AMG88XX, CONFIG_SENSOR_LOG_LEVEL);
static inline void amg88xx_setup_int(const struct amg88xx_config *cfg,
bool enable)
{
unsigned int flags = enable
? GPIO_INT_EDGE_TO_ACTIVE
: GPIO_INT_DISABLE;
gpio_pin_interrupt_configure_dt(&cfg->int_gpio, flags);
}
int amg88xx_attr_set(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
const struct amg88xx_config *config = dev->config;
int16_t int_level = (val->val1 * 1000000 + val->val2) /
AMG88XX_TREG_LSB_SCALING;
uint8_t intl_reg;
uint8_t inth_reg;
if (!config->int_gpio.port) {
return -ENOTSUP;
}
if (chan != SENSOR_CHAN_AMBIENT_TEMP) {
return -ENOTSUP;
}
LOG_DBG("set threshold to %d", int_level);
if (attr == SENSOR_ATTR_UPPER_THRESH) {
intl_reg = AMG88XX_INTHL;
inth_reg = AMG88XX_INTHH;
} else if (attr == SENSOR_ATTR_LOWER_THRESH) {
intl_reg = AMG88XX_INTLL;
inth_reg = AMG88XX_INTLH;
} else {
return -ENOTSUP;
}
if (i2c_reg_write_byte_dt(&config->i2c,
intl_reg, (uint8_t)int_level)) {
LOG_DBG("Failed to set INTxL attribute!");
return -EIO;
}
if (i2c_reg_write_byte_dt(&config->i2c,
inth_reg, (uint8_t)(int_level >> 8))) {
LOG_DBG("Failed to set INTxH attribute!");
return -EIO;
}
return 0;
}
static void amg88xx_gpio_callback(const struct device *dev,
struct gpio_callback *cb, uint32_t pins)
{
struct amg88xx_data *drv_data =
CONTAINER_OF(cb, struct amg88xx_data, gpio_cb);
const struct amg88xx_config *config = drv_data->dev->config;
amg88xx_setup_int(config, false);
#if defined(CONFIG_AMG88XX_TRIGGER_OWN_THREAD)
k_sem_give(&drv_data->gpio_sem);
#elif defined(CONFIG_AMG88XX_TRIGGER_GLOBAL_THREAD)
k_work_submit(&drv_data->work);
#endif
}
static void amg88xx_thread_cb(const struct device *dev)
{
struct amg88xx_data *drv_data = dev->data;
const struct amg88xx_config *config = dev->config;
uint8_t status;
if (i2c_reg_read_byte_dt(&config->i2c,
AMG88XX_STAT, &status) < 0) {
return;
}
if (drv_data->drdy_handler != NULL) {
drv_data->drdy_handler(dev, drv_data->drdy_trigger);
}
if (drv_data->th_handler != NULL) {
drv_data->th_handler(dev, drv_data->th_trigger);
}
amg88xx_setup_int(config, true);
}
#ifdef CONFIG_AMG88XX_TRIGGER_OWN_THREAD
static void amg88xx_thread(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p2);
ARG_UNUSED(p3);
struct amg88xx_data *drv_data = p1;
while (42) {
k_sem_take(&drv_data->gpio_sem, K_FOREVER);
amg88xx_thread_cb(drv_data->dev);
}
}
#endif
#ifdef CONFIG_AMG88XX_TRIGGER_GLOBAL_THREAD
static void amg88xx_work_cb(struct k_work *work)
{
struct amg88xx_data *drv_data =
CONTAINER_OF(work, struct amg88xx_data, work);
amg88xx_thread_cb(drv_data->dev);
}
#endif
int amg88xx_trigger_set(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
struct amg88xx_data *drv_data = dev->data;
const struct amg88xx_config *config = dev->config;
if (!config->int_gpio.port) {
return -ENOTSUP;
}
if (i2c_reg_write_byte_dt(&config->i2c,
AMG88XX_INTC, AMG88XX_INTC_DISABLED)) {
return -EIO;
}
amg88xx_setup_int(config, false);
if (trig->type == SENSOR_TRIG_THRESHOLD) {
drv_data->th_handler = handler;
drv_data->th_trigger = trig;
} else {
LOG_ERR("Unsupported sensor trigger");
return -ENOTSUP;
}
amg88xx_setup_int(config, true);
if (i2c_reg_write_byte_dt(&config->i2c,
AMG88XX_INTC, AMG88XX_INTC_ABS_MODE)) {
return -EIO;
}
return 0;
}
int amg88xx_init_interrupt(const struct device *dev)
{
struct amg88xx_data *drv_data = dev->data;
const struct amg88xx_config *config = dev->config;
if (!gpio_is_ready_dt(&config->int_gpio)) {
LOG_ERR("%s: device %s is not ready", dev->name,
config->int_gpio.port->name);
return -ENODEV;
}
gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT | config->int_gpio.dt_flags);
gpio_init_callback(&drv_data->gpio_cb,
amg88xx_gpio_callback,
BIT(config->int_gpio.pin));
if (gpio_add_callback(config->int_gpio.port, &drv_data->gpio_cb) < 0) {
LOG_DBG("Failed to set gpio callback!");
return -EIO;
}
drv_data->dev = dev;
#if defined(CONFIG_AMG88XX_TRIGGER_OWN_THREAD)
k_sem_init(&drv_data->gpio_sem, 0, K_SEM_MAX_LIMIT);
k_thread_create(&drv_data->thread, drv_data->thread_stack,
CONFIG_AMG88XX_THREAD_STACK_SIZE,
amg88xx_thread, drv_data,
NULL, NULL, K_PRIO_COOP(CONFIG_AMG88XX_THREAD_PRIORITY),
0, K_NO_WAIT);
#elif defined(CONFIG_AMG88XX_TRIGGER_GLOBAL_THREAD)
drv_data->work.handler = amg88xx_work_cb;
#endif
amg88xx_setup_int(config, true);
return 0;
}

View file

@ -0,0 +1,48 @@
# Copyright (c) 2024 PM
# SPDX-License-Identifier: Apache-2.0
description: Melexis MLX90640 32x24 (768) pixel infrared array sensor
compatible: "melexis,mlx90640"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
refresh-rate:
type: int
default: 2
enum:
- 0 # 0.5HZ
- 1 # 1HZ
- 2 # 2HZ
- 3 # 4HZ
- 4 # 8HZ
- 5 # 16HZ
- 6 # 32HZ
- 7 # 64HZ
description:
Refresh rate of the sensor itself.
The I2C must be configured to keep up with the data flow.
This configures the rate for subfields, i.e. the full frame rate is half of this value.
adc-resolution:
type: int
default: 2
enum:
- 0 # 16BIT
- 1 # 17BIT
- 2 # 18BIT
- 3 # 19BIT
description:
ADC resolution.
reading-pattern:
type: int
default: 1
enum:
- 0 # CHESS
- 1 # INTERLEAVE
description:
Reading pattern.
The sensor can be read in either line-interleaved subfields (similar to a TV video)
or a pixel alternating "chess board" pattern.