EMPA Workshop – İstanbul
EMPA Workshop’a hoşgeldiniz, bu yazıda SensNode’un üzerinde bulunan sensörlerin ve wfi modülün nasıl kullanılacağı anlatılacaktır. Lütfen önceki yazımızdaki (EMPA Workshop’a gelmeden önce yapılıcaklar) talimalatları eksiksiz yaptığınızdan emin olunuz.
Projenin kurulumu – STM32CubeMX
1- Bilgisayarınızda yüklü olan STM32CubeMX programını açınız. Sonrasında Acces to Board Selector seçeneğine tıklayanız.
2- Açılan pencerede kullanacağımız kart olan NUCLEO-U575ZI-Q kartını Commercial Part Number bölgesinde aratıp, Board List kısmında çıkan kartı çift tıklayarak seçiniz.
3- Trustzone Feature bildirimi çıkarsa Without TrustZone Activated seçeneğini seçiniz.
4- Seçilen kartın MCU kurulum (configuration) sayfası geldiğinde sistemi hazırlamaya başlayabilirsiniz.
5- Öncelikle Connectivity kısmından başlayınız. Connectivity sekmesinden (1) I2C1’i seçiniz (2). Mode penceresinde I2C‘yi seçip aktif hale getiriniz (3). Parameter Settings kısmından I2C speed Mode‘unu Fast Mode olarak seçiniz (4). Pinout View kısmında pinleri değiştirmeniz gerekecektir. Bu sistemde PB9 ve PB8 pinleri I2C olarak seçilmiştir. Pinlere tıklayıp I2C1 seçeneklerini seçiniz (5).
6- ESP32 ve STM32 arasındaki UART bağlantısını kurmak için LPUART seçeneğinin aktif edilmesi gerekmektedir. Connectivity altında LPUART1 seçeneğini seçip (1) Mode olarak Asynchronous modunu seçiniz (2). Baud Rate seçeneğini 115200 olarak belirleyiniz (3). Pinout View kısmında PG8 ve PG7 pinlerini LPUART1_RX ve LPUART1_ TX olarak seçiniz (4).
7- Sırada GPIO ayarları yapılacaktır. Pinout View kısmında öncelikle Interrupt’ları ayarlayınız. Bir pini interrupt olarak ayarlamak için GPIO_EXTIx olarak seçmek gerekmektedir.
8- Pinin üstüne gelip sağ tıkladığınızda çıkan menüden Enter User Label ile istediğiniz bir isim belirleyebilirsiniz. Örneğin PD15 pini ISM330DHCX’in ikinci interrupt pinine bağlı olduğu için PD15’i GPIO_EXTI15 olarak seçip INT2_ISM330 etiketini ekleyiniz
9- Yukarıdaki adımlarda olduğu şekilde diğer sensörlerin de interrupt pinlerini ayarlayınız. Aşağıdaki listede yer alan pinleri 7. ve 8. adımlarda gösterildiği gibi ayarlayabilirsiniz.
- ISM330DHCX Birinci Interrupt pini -> PF12
- ISM330DHCX İkinci Interrupt pini -> PD15
- VL53L3 Interrupt pini -> PA6
- LPS22 Interrupt pini -> PE9
Sensörlerin interrupt pinlerini ve MCU’nun hangi pinlerine bağlandıklarını kartların şemaları üzerinden de görebilirsiniz. Şemalara aşağıdan ulaşabilirsiniz:
10a- VL53L3 interrupt’ı bize falling edge bir interrupt veriyor, bu yüzden kartımızda GPIO menüsünden GPIO mode ayarını External Interrupt Mode With Falling edge trigger seçeneğini seçiyoruz.
10b- Interrupt pinlerini ayarladıktan sonra “System Core” sekmesindeki GPIO kısmından NVIC “Interrupt”ları etkinleştirmelisiniz.
11- Interrupt ayarlarından sonra diğer pin ayarlarını yapabilirsiniz. Pinout View kısmından kullanılacak olan pinleri, ilgili pin numaralarına sol tıklayarak GPIO_Output seçeneğini seçip ayarlayabilirsiniz. GPIO pinlerine User Label eklemek, kod içinde portları bulmanızı kolaylaştıracaktır.
12- Yukarıdaki maddede anlatıldığı şekilde tüm GPIO Output pinlerini ayarlamalısınız. Pinleri aşağıdaki listede görebilirsiniz:
- VL53L3 Enable(EN_VL53L3) -> PA5
- SHT30 Reset Pin(RST_SHT30) -> PF13
- ESP32 Enable(ESP_EN) -> PF15
- LED0 -> PA3
- LED1 -> PA2
- LED2 -> PC3
- LED3 -> PB0
- LED4 -> PC1
- LED5 -> PC0
Pinleri kolayca bulmak için Pinout View penceresinin alt kısmındaki arama kutusunu kullanabilirsiniz.
13- Kullandığımız Sensörlerin driverlarını indirmek için X-CUBE-MEMS ve X-CUBE_TOF uygulama paketlerini projemize eklememiz lazım. Yukarıda bulunan Software Packs sekmesinden Select Components‘a tıklayın.
14-Açılan sekmede X-CUBE-MEMS1 paketini bulup tıklayarak açın.
15- Board part yazan yerlerden driverlarımız indireceğiz. AccGyr seçeniğinin içerisinden ISM330DHCX sensörün Selectionını I2C olarak değiştirin. Aynı işlemi PressTemp içerisinde bulunan LPS22DF içinde yapınız.
15- X-CUBE-MEMS sekmesini kapatıp X-CUBE-TOF1 sekmesine gidiniz. Board Part Ranging sekmesinden VL53L3CX sensörüne ait olan kutucuyu işaretleyiniz. Bu işlemden sonra Ok’e tıklayabilirsiniz.
16- Ana menüde soldaki kategorilerde Software Packs sekmesinin geldiğini görüceksiniz. Buradan paketlerimizin aktivasyonlarını yapmamız gerek. Software Packs sekmesinden X-CUBE-MEMS seçeneğine tıklayınız. Sonra işaretlenmemiş olan Board Part AccGyr ve Boar Part PressTemp seçeneklerini işaretleyiniz.
17- Seçenekleri işaretledikten sonra Platform Settings bölümüne gidip I2C pinlerini ayarlamamız gerekiyor.
18- Aynı işlemi X-CUBE-TOF1 için de yapmamız gerekiyor.
19- I2C, LPUART, Interrupt pinleri ve Output pinleri hazırlandıktan sonra projeyi kaydedip kodu oluşturabilirsiniz. Project Manager sekmesinden (1) öncelikle Project Name kısmını doldurunuz (2). Sonrasında Toolchain/IDE olarak STM32CubeIDE seçeneğini seçiniz (3). Son olarak Generate Code butanuna basınız (4).
Böylelikle projenizin konfigürasyon dosyalarının da olduğu kod üretilmiştir.
Sensörlerin Başlatılması ve Veri Alım Yöntemleri
SensNode üzerindeki sensörlerden veri çekmenin ve interrupt özelliklerinin ayarlanması anlatılacaktır.
Aşağıdaki dosyaları githubdan indirip oluşturuduğunuz projenin içerisine kopyalayanız. C dosyaları => src , h dosyaları => inc.
1- SHT Driverlarını yüklemek için aşağıdaki linkten sht3x.c ve sht3x.h dosyalarını projenize yükleyiniz:
Link1: https://github.com/henriheimann/stm32-hal-sht3x
2- Machine Learning Core örnek data setleri için aşağıdaki linkten istediğiniz data seti indirebilirsiniz (örnek olarak ism330dhcx_vibration_monitoring.h dosyasını projenize dahil etmeniz yeterli). workshop sırasında görüceğiniz örnek bir vibration monitoring örneğidir:
Link2: https://github.com/STMicroelectronics/STMems_Machine_Learning_Core/tree/master/application_examples/ism330dhcx
SHT30 Polling
Aşağıdaki kodu main.c dosyasının içindeki belirtilen yerlere kopyalayınız. Main.c içerisinde eklemelerinizi User Code alanlarına eklemelisiniz.
Öncelikle header dosyalarını /* USER CODE BEGIN Includes */ alanının altına yapıştırınız.
/* USER CODE BEGIN Includes */
#include "stm32u5xx_nucleo_bus.h"
#include "ism330dhcx.h"
#include "lps22df.h"
#include "vl53l3cx.h"
#include "sht3x.h"
/* USER CODE END Includes */
Bu kodda Pre-define kullanılıcaktır. #define ile sadece tanımlanmış kısımların derlenmesi sağlanacaktır.
SHT örneğine başlamadan sht3x.h dosyasına girerek, “#error Platform not implemented” yazan satırı #include “stm32u5xx_hal.h” ile değiştirin.
Böylelikle sht driverı içerisinde hal drivera ulaşım sağlanabilecektir. (Eğer başka bir kart kullanıyorsanız include edeceğiniz hal dosyası değişiklik gösterebilir.)
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define SHT_POLL
/* USER CODE END PD */
Log mesajlarını printf yardımıyla almak için aşağıdaki kodu ekleyiniz.
/* USER CODE BEGIN PV */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
PUTCHAR_PROTOTYPE {
/* Place your implementation of fputc here */
/* e.g. write a character to the USART2 and Loop until the end of transmission */
while (HAL_OK != HAL_UART_Transmit(&huart1, (uint8_t*) &ch, 1, 30000)) {
;
}
return ch;
}
/* USER CODE END PV */
SHT30 ve diğer sensörlerin verileri I2C üzerinden okunacağı için I2C’nin başlatılması gerekmektedir. Bunun için aşağıdaki kodu ekleyiniz.
/* USER CODE BEGIN PFP */
int32_t sensor_probing();
/* USER CODE BEGIN 4 */ alanına aşağıdaki kodu ekleyiniz.
/* USER CODE BEGIN 4 */
int32_t sensor_probing() {
int32_t ret = 0;
uint16_t id;
#if defined(SHT_POLL)
BSP_I2C1_Init();
#endif
#if defined(LPS_INT_FIFO)
LPS22DF_Capabilities_t cap_lps;
lps_ctx.BusType = LPS22DF_I2C_BUS;
lps_ctx.Address = LPS22DF_I2C_ADD_H;
lps_ctx.Init = BSP_I2C1_Init;
lps_ctx.DeInit = BSP_I2C1_DeInit;
lps_ctx.ReadReg = BSP_I2C1_ReadReg;
lps_ctx.WriteReg = BSP_I2C1_WriteReg;
lps_ctx.GetTick = BSP_GetTick;
if (LPS22DF_RegisterBusIO(&lps22df_obj_o, &lps_ctx) != LPS22DF_OK) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else if (LPS22DF_ReadID(&lps22df_obj_o, &id) != LPS22DF_OK) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else if (id != LPS22DF_ID) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else {
(void) LPS22DF_GetCapabilities(&lps22df_obj_o, &cap_lps);
}
#endif
#if defined(ISM_FIFO) || defined(ISM_INT_ML) || defined(ISM_TAP)
ISM330DHCX_Capabilities_t cap_ism;
ism_ctx.BusType = ISM330DHCX_I2C_BUS;
ism_ctx.Address = ISM330DHCX_I2C_ADD_H;
ism_ctx.Init = BSP_I2C1_Init;
ism_ctx.DeInit = BSP_I2C1_DeInit;
ism_ctx.ReadReg = BSP_I2C1_ReadReg;
ism_ctx.WriteReg = BSP_I2C1_WriteReg;
ism_ctx.GetTick = BSP_GetTick;
if (ISM330DHCX_RegisterBusIO(&ism330dh_obj_o, &ism_ctx) != ISM330DHCX_OK) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else if (ISM330DHCX_ReadID(&ism330dh_obj_o, &id) != ISM330DHCX_OK) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else if (id != ISM330DHCX_ID) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else {
(void) ISM330DHCX_GetCapabilities(&ism330dh_obj_o, &cap_ism);
}
#endif
#if defined(TOF_INT)
VL53L3CX_IO_t vl_ctx;
VL53L3CX_Capabilities_t cap_vl;
vl_ctx.Address = VL53L3CX_DEVICE_ADDRESS;
vl_ctx.Init = BSP_I2C1_Init;
vl_ctx.DeInit = BSP_I2C1_DeInit;
vl_ctx.ReadReg = BSP_I2C1_Recv;
vl_ctx.WriteReg = BSP_I2C1_Send;
vl_ctx.GetTick = BSP_GetTick;
HAL_GPIO_WritePin(EN_VL53L3_GPIO_Port, EN_VL53L3_Pin, 1); // Reset ToF sensor
HAL_Delay(10);
HAL_GPIO_WritePin(EN_VL53L3_GPIO_Port, EN_VL53L3_Pin, 0); // Reset ToF sensor
HAL_Delay(10);
HAL_GPIO_WritePin(EN_VL53L3_GPIO_Port, EN_VL53L3_Pin, 1); // Reset ToF sensor
HAL_Delay(10);
VL53L3CX_SetPowerMode(&vl53l3_obj_o, 0);
if (VL53L3CX_RegisterBusIO(&vl53l3_obj_o, &vl_ctx) != VL53L3CX_OK) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else if (VL53L3CX_ReadID(&vl53l3_obj_o, &id) != VL53L3CX_OK) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else if (id != VL53L3CX_ID) {
ret = BSP_ERROR_UNKNOWN_COMPONENT;
} else {
(void) VL53L3CX_GetCapabilities(&vl53l3_obj_o, &cap_vl);
}
#endif
return ret;
}
Main fonksiyonu içinde /* USER CODE BEGIN 2 */ kısmına aşağıdaki kodu ekleyiniz.
int32_t stat;
HAL_Delay(300);
stat = sensor_probing();
if (stat != 0) {
while (1)
;
}
SHT30 polling bir fonksiyon içerisinde çalışacaktır. Bu fonksiyonun prototipini gerekli yere yazınız.
/* USER CODE BEGIN PFP */
#ifdef SHT_POLL
void sht_main(void);
#endif
/* USER CODE END PFP */
SHT30 fonksiyonunun main içerisinde çağrılacağı yer “while”dan hemen öncedir.
int main(void) {
*
*
*
/* USER CODE BEGIN 2 */
int32_t stat;
HAL_Delay(300);
stat = sensor_probing();
if (stat != 0) {
while (1)
;
}
#ifdef SHT_POLL
sht_main();
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Son olarak sht_polling_main(void) fonksiyonunu yazınız. Bunun için /* USER CODE BEGIN 4 */ bölgesini kullanınız.
/* USER CODE BEGIN 4 */
#ifdef SHT_POLL
float temperature, humidity;
extern I2C_HandleTypeDef hi2c1;
void sht_main() {
sht3x_handle_t handle = { .i2c_handle = &hi2c1, .device_address =
SHT3X_I2C_DEVICE_ADDRESS_ADDR_PIN_LOW };
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_RESET); // SHT30 reset Pin
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(100);
sht3x_init(&handle);
while (1) {
sht3x_read_temperature_and_humidity(&handle, &temperature, &humidity);
printf("Temperature: %dC, Humidity:%d%%RH\n\r ", (int) temperature,
(int) humidity);
HAL_Delay(1000);
}
}
#endif
/* USER CODE END 4 */
Bu fonksiyon ile 2 saniyede bir sıcaklık ve nem verisi “poll” metodu ile ekrana basılacaktır.
SHT30 sensörünü init edebilmek için SHTSensInit(&hi2c1); fonksiyonu çağrılıyor. Veriyi “Poll” metodu ile almak için ise read_SHT30(&hi2c1, &temperature, &humidity); kodu kullanılıyor.
STM32CubeIDE içerisinde üsteki kısayol tuşlarından Run tuşuna basarak programı derleyip yükleyebilirsiniz.
Bir sonraki kod’a geçmeden önce #define SHT_POLL satırını yoruma alarak bu yapılanları derlemeden çıkarabilirsiniz.
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
//#define SHT_POLL
/* USER CODE END PD */
LPS22DF Fifo Interrupt
Öncellikle yeni “define” bilgisini ekleyiniz.
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define LPS_INT_FIFO
/* USER CODE END PD */
Fonksiyonun prototipini gerekli yere yazınız.
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
#ifdef LPS_INT_FIFO
static LPS22DF_Object_t lps22df_obj_o;
LPS22DF_IO_t lps_ctx;
void LPS_INT_FIFO_main(LPS22DF_Object_t *pObj);
#endif
/* USER CODE END PFP */
Main fonksiyonu içerisinde LPS FIFO Interrupt fonksiyonunu “while”dan önce çağırınız.
int main(void) {
*
*
*
/* USER CODE BEGIN 2 */
#ifdef LPS_INT_FIFO
LPS_INT_FIFO_main(&lps22df_obj_o);
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Fonksiyonu /* USER CODE BEGIN 4 */ alanına yazınız.
/* USER CODE BEGIN 4 */
#if defined(LPS_POLL) || defined(LPS_INT_FIFO)
static uint8_t lpsInt = 0;
void LPS_INT_FIFO_main(LPS22DF_Object_t *pObj) {
lps22df_bus_mode_t bus_mode;
lps22df_stat_t status;
HAL_Delay(30);
lps22df_init_set(&(pObj->Ctx), LPS22DF_RESET);
do {
lps22df_status_get(&(pObj->Ctx), &status);
} while (status.sw_reset);
HAL_Delay(30);
lps22df_init_set(&(pObj->Ctx), LPS22DF_DRV_RDY);
bus_mode.filter = LPS22DF_AUTO;
bus_mode.interface = LPS22DF_SEL_BY_HW;
lps22df_bus_mode_set(&(pObj->Ctx), &bus_mode);
lps22df_md_t md;
md.odr = LPS22DF_25Hz;
md.avg = LPS22DF_4_AVG;
md.lpf = LPS22DF_LPF_ODR_DIV_4;
if(lps22df_mode_set(&(pObj->Ctx), &md) != 0){
while(1);
}
lps22df_fifo_md_t fifoConfig;
fifoConfig.operation = LPS22DF_STREAM_TO_FIFO;
fifoConfig.watermark = 5;
lps22df_fifo_mode_set(&(pObj->Ctx), &fifoConfig);
lps22df_int_mode_t intmodeconfig;
intmodeconfig.active_low = PROPERTY_DISABLE;
intmodeconfig.drdy_latched = PROPERTY_DISABLE;
intmodeconfig.int_latched = PROPERTY_ENABLE;
lps22df_interrupt_mode_set(&(pObj->Ctx), &intmodeconfig);
lps22df_pin_int_route_t int_route;
int_route.drdy_pres = PROPERTY_DISABLE;
int_route.fifo_ovr = PROPERTY_DISABLE;
int_route.fifo_th = PROPERTY_ENABLE;
int_route.fifo_full = PROPERTY_DISABLE;
lps22df_pin_int_route_set(&(pObj->Ctx), &int_route);
printf("Ready\r\n");
lps22df_fifo_data_t data[5];
lpsInt = 0;
while (1) {
if (lpsInt == 1) {
lpsInt = 0;
lps22df_fifo_data_get(&(pObj->Ctx), 5, data);
fifoConfig.operation = LPS22DF_BYPASS;
fifoConfig.watermark = 5;
lps22df_fifo_mode_set(&(pObj->Ctx), &fifoConfig);
lps22df_pin_int_route_get(&(pObj->Ctx), &int_route);
lps22df_pin_int_route_set(&(pObj->Ctx), &int_route);
lps22df_fifo_mode_get(&(pObj->Ctx), &fifoConfig);
fifoConfig.operation = LPS22DF_STREAM_TO_FIFO;
lps22df_fifo_mode_set(&(pObj->Ctx), &fifoConfig);
printf("[\n\r");
for (uint8_t l = 0; l < 5; l++) {
printf("pre %d : %dhPa \n\r", l, (int) (data[l].hpa));
}
printf("]\n\r");
}
}
}
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == INT_LPS22_Pin) {
lpsInt = 1;
}
}
#endif
LPS22DF’nin bu örneğinde “watermark” değeri 5 olan bir “FIFO” yapılmıştır. Sistemin Interrupt üretmesi için de watermark seviyesine ulaşılması gerekmektedir. Watermark değerini fonksiyon içerisinden değiştirilebilmektedir.
Sisteme bir interrupt geldiğinde sistem Callback fonksiyonlarını çağırır. Interrupt almak için kullanılacak Callback fonksiyonu yukarıdadır. Bu fonksiyon içerisinde basit bir if koşulu ile Interrupt’ın beklediğimiz yerden gelip gelmediği kontrol edilebilir.
Bir sonraki koda geçerken define’ı yoruma alınız.
ISM330DHCX FIFO Interrupt
Öncellikle yeni “define” bilgisini ekleyiniz.
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ISM_FIFO
/* USER CODE END PD */
Fonksiyonun prototipini gerekli yere yazınız.
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
#ifdef ISM_FIFO
static ISM330DHCX_Object_t ism330dh_obj_o;
ISM330DHCX_IO_t ism_ctx;
void ism_main_fifo();
#endif
/* USER CODE END PFP */
Main fonksiyonu içerisinde ISM FIFO Interrupt fonksiyonunu “while”dan önce çağırınız.
int main(void) {
*
*
*
/* USER CODE BEGIN 2 */
#ifdef ISM_FIFO
ism_main_fifo();
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Fonksiyonu /* USER CODE BEGIN 4 */ alanına yazınız.
/* USER CODE BEGIN 4 */
#ifdef ISM_FIFO
void ism_main_fifo() {
printf("ISM330 Demo\r\n");
HAL_Delay(300);
ISM330DHCX_Init(&ism330dh_obj_o);
ISM330DHCX_ACC_SetOutputDataRate(&ism330dh_obj_o, 1666.0f);
ISM330DHCX_ACC_Enable(&ism330dh_obj_o);
ISM330DHCX_FIFO_ACC_Set_BDR(&ism330dh_obj_o,
ISM330DHCX_XL_BATCHED_AT_1667Hz);
ISM330DHCX_FIFO_Set_INT1_FIFO_Full(&ism330dh_obj_o, 1);
ISM330DHCX_FIFO_Set_Watermark_Level(&ism330dh_obj_o, 91);
ISM330DHCX_FIFO_Set_Stop_On_Fth(&ism330dh_obj_o, 1);
ISM330DHCX_FIFO_Set_Mode(&ism330dh_obj_o, ISM330DHCX_STREAM_TO_FIFO_MODE);
while (1) {
}
}
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {
ISM330DHCX_Axes_t *Acceleration;
uint16_t sample = 0;
ISM330DHCX_FIFO_Get_Num_Samples(&ism330dh_obj_o, &sample);
printf("[DATA ##] ACC_X ACC_Y ACC_Z\r\n");
for (int i = 0; i < sample; i++) {
ISM330DHCX_FIFO_ACC_Get_Axes(&ism330dh_obj_o, Acceleration);
printf("%d %d %d %d\r\n", i, Acceleration->x, Acceleration->y,
Acceleration->z);
}
}
Bu kodda ACC verilerinin FIFO’da, seçilen watermak kadar doldurulup interrupt vermesi hedeflenmiştir.
Bir sonraki koda geçerken define’ı yoruma alınız.
ISM330DHCX Single and Double Tap Interrupt
Öncellikle yeni “define” bilgisini ekleyiniz.
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ISM_TAP
/* USER CODE END PD */
Fonksiyonun prototipini gerekli yere yazınız.
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
#ifdef ISM_TAP
static ISM330DHCX_Object_t ism330dh_obj_o;
ISM330DHCX_IO_t ism_ctx;
void ism_main_tap();
#endif
/* USER CODE END PFP */
Main fonksiyonu içerisinde ISM TAP fonksiyonunu “while”dan önce çağırınız.
int main(void) {
*
*
*
/* USER CODE BEGIN 2 */
#ifdef ISM_TAP
ism_main_tap();
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Fonksiyonu /* USER CODE BEGIN 4 */ alanına yazınız.
/* USER CODE BEGIN 4 */
#if defined(ISM_TAP)
void ism_main_tap() {
printf("ISM330 Demo \r\n");
HAL_Delay(300);
ISM330DHCX_Init(&ism330dh_obj_o);
ISM330DHCX_ACC_Enable(&ism330dh_obj_o);
ISM330DHCX_ACC_Enable_Double_Tap_Detection(&ism330dh_obj_o, ISM330DHCX_INT1_PIN);
ISM330DHCX_ACC_Enable_Single_Tap_Detection(&ism330dh_obj_o, ISM330DHCX_INT2_PIN);
while(1);
}
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin == INT1_ISM330_Pin){
printf("Double TAP\r\n");
}
else if(GPIO_Pin == INT2_ISM330_Pin){
printf("Single TAP\r\n");
}
}
#endif
Bu fonksiyonda iki farklı interrupt hazırlanmıştır. Single ve Double olarak ayrılan bu iki interrupt ISM330DHCX’in iki ayrı interrupt pininden gelmektedir. Callback fonksiyonunda interrupt’ın hangi pinden geldiğine bakılarak single/double tap ayırt edilebilir.
Bir sonraki koda geçerken define’ı yoruma alınız.
ISM330DHCX Machine Learning Core
Öncellikle yeni “define” bilgisini ekleyiniz.
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define ISM_INT_ML
/* USER CODE END PD */
Fonksiyonun prototipini gerekli yere yazınız.
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
#ifdef ISM_INT_ML
static ISM330DHCX_Object_t ism330dh_obj_o;
ISM330DHCX_IO_t ism_ctx;
void ism_main_ml();
#endif
/* USER CODE END PFP */
Main Main fonksiyonu içerisinde ISM MLC INT fonksiyonunu “while”dan önce çağırınız.
int main(void) {
*
*
*
/* USER CODE BEGIN 2 */
#ifdef ISM_INT_ML
ism_main_ml();
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Fonksiyonu /* USER CODE BEGIN 4 */ alanına yazınız.
/* USER CODE BEGIN 4 */
#if defined(ISM_INT_ML)
#include "ism330dhcx_six_d_position.h"
#include "ism330dhcx_vehicle_stationary_detection.h"
#include "ism330dhcx_vibration_monitoring.h"
static int16_t data_raw_acceleration[3];
static float acceleration_mg[3];
static uint8_t tx_buffer[1000];
void ism_init(ISM330DHCX_Object_t *pObj) {
ism330dhcx_pin_int1_route_t pin_int1_route;
static uint8_t rst;
ism330dhcx_reset_set(&(pObj->Ctx), PROPERTY_ENABLE);
do {
ism330dhcx_reset_get(&(pObj->Ctx), &rst);
} while (rst);
for (int i = 0; i < (sizeof(ism330dhcx_vibration_monitoring) / sizeof(ucf_line_t));
i++) {
ism330dhcx_write_reg(&(pObj->Ctx), ism330dhcx_vibration_monitoring[i].address,
(uint8_t*) &ism330dhcx_vibration_monitoring[i].data, 1);
}
ism330dhcx_pin_int1_route_get(&(pObj->Ctx), &pin_int1_route);
pin_int1_route.mlc_int1.int1_mlc1 = PROPERTY_ENABLE;
ism330dhcx_pin_int1_route_set(&(pObj->Ctx), &pin_int1_route);
/* Configure interrupt pin mode notification */
ism330dhcx_int_notification_set(&(pObj->Ctx),
ISM330DHCX_BASE_LATCHED_EMB_PULSED);
HAL_Delay(400);
}
uint8_t ml_it=0;
void ACC_Values(ISM330DHCX_Object_t *pObj) {
ism330dhcx_mlc_status_mainpage_t status;
while (1) {
// ism330dhcx_mlc_status_get(&(pObj->Ctx), &status);
if (ml_it) {
ml_it = 0;
uint8_t vehicle_stationary_detection[3];
ism330dhcx_mlc_out_get(&(pObj->Ctx), vehicle_stationary_detection);
switch(vehicle_stationary_detection[0]){
case 0:
printf("No Vibration\r\n");
break;
case 1:
printf("Low Vibration\r\n");
break;
case 2:
printf("High Vibration\r\n");
break;
}
}
}
}
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {
ml_it = 1;
}
void ism_main_ml() {
printf("ISM330 Demo \r\n");
HAL_Delay(300);
ism_init(&ism330dh_obj_o);
HAL_Delay(300);
printf("Here\r\n");
ACC_Values(&ism330dh_obj_o);
/* Start device configuration. */
}
#endif
Bu fonksiyon machine learning core ism330dhcx_vibration_monitoring örneğini içermektedir. Vibrasyon seviyesini UART ile takip edebilirsiniz. Ayrıca ism330dhcx_vibration_monitoring yerine ism330dhcx_six_d_position ve ism330dhcx_vehicle_stationary_detection yazarak bu örnekleri de çalıştırabilirsiniz.
Bu kodda ST’nin sağladığı modeller örnek olarak kullanılmıştır. Dilerseniz kendiniz de veriler toplayıp, bu veriler ile oluşturulan modeller ile machine learning core örneklerini çoğaltabilirsiniz. İlgili uygulama notuna aşağıdan ulaşabilirsiniz:
Bir sonraki kod’a geçerken define’ı yoruma alınız.
Time Of Flight İnterrupt
Öncellikle yeni “define” bilgisini ekleyiniz.
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define TOF_INT
/* USER CODE END PD */
Fonksiyonun prototipini gerekli yere yazınız.
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
#ifdef TOF_INT
static VL53L3CX_Object_t vl53l3_obj_o;
void tof_polling_main(void);
#endif
/* USER CODE END PFP */
Main Main fonksiyonu içerisinde TOF fonksiyonunu “while”dan önce çağırınız.
int main(void) {
*
*
*
/* USER CODE BEGIN 2 */
#ifdef TOF_INT
tof_polling_main();
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Fonksiyonu /* USER CODE BEGIN 4 */ alanına yazınız.
/* USER CODE BEGIN 4 */
#if defined(TOF_INT)
uint8_t IntCount = 0;
void tof_polling_main() {
while (VL53L3CX_Init(&vl53l3_obj_o) != VL53L3CX_OK)
;
printf("VL53L3 Polling Demo\r\n");
VL53L3CX_ProfileConfig_t profile;
profile.RangingProfile = VL53L3CX_PROFILE_MEDIUM;
profile.TimingBudget = 100; /* 16 ms < TimingBudget < 500 ms */
profile.Frequency = 1;
profile.EnableAmbient = 1;
profile.EnableSignal = 1;
VL53L3CX_ConfigProfile(&vl53l3_obj_o, &profile);
VL53L3CX_Start(&vl53l3_obj_o, VL53L3CX_MODE_BLOCKING_CONTINUOUS);
VL53L3CX_Result_t Result;
int32_t status = 0;
VL53LX_ClearInterruptAndStartMeasurement(&vl53l3_obj_o);
IntCount = 0;
while (1) {
if (IntCount != 0) {
IntCount = 0;
status = VL53L3CX_GetDistance(&vl53l3_obj_o, &Result);
if (status == BSP_ERROR_NONE) {
printf("\nTargets = %lu",
(unsigned long) Result.ZoneResult[0].NumberOfTargets);
for (int i = 0; i < Result.ZoneResult[0].NumberOfTargets; i++) {
printf("\n |--. ");
printf("Status = %ld, Distance = %5ld mm ",
(long) Result.ZoneResult[0].Status[i],
(long) Result.ZoneResult[0].Distance[i]);
}
VL53LX_ClearInterruptAndStartMeasurement(&vl53l3_obj_o);
printf("\r\n");
}
}
}
}
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin) {
IntCount = 1;
}
/* USER CODE END 4 */
Time of flight sensörü her data hazır olduğunda bir interrupt üretir. TOF Polling ve bu interrupt kodunun arasındaki farklardan biri dataready fonksiyonu gerekmez ve while döngüsüne girmeden önce interrupt’lar yenilenir.
STM32U5 ve ThreadX
ThreadX kodunu oluşturmak için yeni bir proje başlatmamız gerekir. Pinler ve konfigürasyonları tekrar ayarlamamak için STM32CubeIDE içerisinde File->New->STM32 Project from existing ioc seçeneğini seçiniz.
Karşınıza çıkan pencerede önceki projenizde kullandığınız ioc dosyasını STM32CubeMX.ioc file bölgesine giriniz. Yeni dosyanızın ismini belirledikten sonra Finish tuşuna basınız.
Karşınıza Pinout & Configuration ekranı gelince Middleware -> THREADX seçeneğine gidiniz. Sonra Core seçeneğini aktif ediniz. Aşağıda açılan Configuration sekmesinden Memory Configuration bölümüne gidip ThreadX memory pool size kısmına (3*1024) değerini giriniz.
ThreadX Timebase source olarak systick tışında bir timer istemektedir, bu yüzden TIM6’yı Timebase source olarak seçiniz.
Sistemin daha optimize çalışması için ICACHE açılması öneriliyor.
Üst sekmelerden Project Manager sekmesine gidiniz. Thread-Safe Setting kısmından Enable multi-threaded support seçeneğini açınız. Sonrasında programı oluşturmak için “CTRL + S” yapınız.
ThreadX File ayarlanması
Bu uygulamada yapmak istenen iki ledin farklı threadler ile kontrol edilmesi ve farklı log mesajları vermesidir.
Öncelikle app_thread.c dosyasına gidiniz. Pinleri ve APP Pool Size’ı çekebilmek için aşağıdaki iki header dosyasını ekleyiniz.
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "app_azure_rtos_config.h"
#include "main.h"
/* USER CODE END Includes */
Tanımlamaları ayarlamak için app_thread.h dosyasına gidiniz. Aşağıdaki tanımları ekleyiniz.
/* Private defines -----------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define APP_STACK_SIZE 1024
#define THREAD_ONE_PRIO 10
#define THREAD_ONE_PREEMPTION_THRESHOLD THREAD_ONE_PRIO
#define MAIN_THREAD_PRIO 5
#define MAIN_THREAD_PREEMPTION_THRESHOLD MAIN_THREAD_PRIO
/* USER CODE END PD */
Scheduler hazırlıklarını yaptıktan sonra app_thread.c içerisindeki App_ThreadX_Init fonksiyonu çağırılır. Thread’lerin memory’lerinin allocate edileceği ve başlatılacağı yer bu fonksiyonun içerisidir. Öncesinde Thread’lerin değişkenlerini ve entry point’lerini global olarak tanımlayınız.
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
TX_THREAD MainThread;
TX_THREAD ThreadOne;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
void MainThread_Entry(ULONG thread_input);
void ThreadOne_Entry(ULONG thread_input);
/* USER CODE END PFP */
Artık thread’leri oluşturabilirsiniz.
UINT App_ThreadX_Init(VOID *memory_ptr) {
UINT ret = TX_SUCCESS;
TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*) memory_ptr;
/* USER CODE BEGIN App_ThreadX_MEM_POOL */
/* USER CODE END App_ThreadX_MEM_POOL */
/* USER CODE BEGIN App_ThreadX_Init */
CHAR *pointer;
/* Allocate the stack for MainThread. */
if (tx_byte_allocate(byte_pool, (VOID**) &pointer,
APP_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS) {
ret = TX_POOL_ERROR;
}
/* Create MainThread. */
if (tx_thread_create(&MainThread, "Main Thread", MainThread_Entry, 0,
pointer, APP_STACK_SIZE,
MAIN_THREAD_PRIO, MAIN_THREAD_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) {
ret = TX_THREAD_ERROR;
}
if (tx_byte_allocate(byte_pool, (VOID**) &pointer,
APP_STACK_SIZE_T2, TX_NO_WAIT) != TX_SUCCESS) {
ret = TX_POOL_ERROR;
}
/* Create ThreadOne. */
if (tx_thread_create(&ThreadOne, "Thread One", ThreadOne_Entry, 0,
pointer, APP_STACK_SIZE_T2,
THREAD_ONE_PRIO, THREAD_ONE_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS) {
ret = TX_THREAD_ERROR;
}
return ret;
}
Bir thread oluştururken önce o thread için memory stack allocatele edilir. Sonrasında thread, pointer’ın olduğu stack’te başlatılır. Aşağıdaki figurde gösterilen pointer stacklerin giriş ve çıkışlarını tutmak içindir.
Threadlerin entry pointlerinin prototipleri yukarıda yazılmıştı. Şimdi fonksiyonları oluşturabilirsiniz.
/* USER CODE BEGIN 1 */
void MainThread_Entry(ULONG thread_input) {
printf("Main thread started\n\r");
while(1){
printf("Green Toggled\n\r");
HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
tx_thread_sleep(100); // 1sn
}
}
void ThreadOne_Entry(ULONG thread_input){
printf("Thread One started\n\r");
while(1){
printf("Red Toggled\n\r");
HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);
tx_thread_sleep(200); // 2sn
}
}
/* USER CODE END 1 */
Printf kodunu kullanmak için aşağıdaki eklentilerin yapılması gerekmektedir. Extern ile başka bir dosyada olan variable bu dosyaya çekilebilir.
app_thread.c içerisinde:
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
TX_THREAD MainThread;
TX_THREAD ThreadOne;
extern UART_HandleTypeDef huart1;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
PUTCHAR_PROTOTYPE {
/* Place your implementation of fputc here */
/* e.g. write a character to the USART2 and Loop until the end of transmission */
while (HAL_OK != HAL_UART_Transmit(&huart1, (uint8_t*) &ch, 1, 30000)) {
;
}
return ch;
}
void MainThread_Entry(ULONG thread_input);
void ThreadOne_Entry(ULONG thread_input);
/* USER CODE END PFP */
Program çalışmaya hazırdır. Run tuşuna basıp programı yükleyebilirsiniz.
ThreadX ve EMPA Cloud
STM32 ThreadX’i ve ESP32 arasındaki iletişimi ve protokolleri kullanarak EMPA CLoud’a veri yollayabilen kod aşağıdaki linktedir.
Yukarıdaki dosyayı indirip .project dosyasına tıklayınız. Proje STM32CubeIDEnizde hazır olduğunda Run tuşuna basarak projeyi NUCLEO-U575 kartına yükleyeniz.
Program Çalıştıktan Sonra kart üzerinde Mavi ve Yeşil ledler yanıcaktır.
- Mavi = 1 , Yeşil = 0 -> ESP32C3 modül internete bağlanmak için wifi bilgilerini bekliyor. (Program için iletişime geçiniz.)
- Mavi = 0 , Yeşil = 1 -> İnternete bağlandı (yaklaşık 10sn içerisinde mavi ışık yanmazsa reset atınız.)
- Mavi = 1 , Yeşil = 1 -> ESP32C3 modül EmpaCloud’a bağlandı ve veri alışverişi için hazır.
ESP32C3 modül EmpaCloud’a bağlandıktan sonra cihazınızın verilerini aşağıdaki linkten kontrol edebilirsiniz.