EMPA Workshop

EMPA Workshop’a hoşgeldiniz, bu yazıda SensNode’un üzerinde bulunan sensörleri ve entegreleri 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 CubeMX

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 board olan NUCLEO-U575ZI-Q “board”unu Commercial Part Number bölgesinde aratıp, Board List’de çıkana çift tıklayınız ve pop-up’da Yes seçeneğine tıklayınız.

3- Trustzone Feature pop-up’ı çıktığından Without TrustZone activated seçeneğini seçiniz.

4- Seçtiğimiz Boardun Config sayfası karşımıza geldiğinde sistemi hazırlamaya başlayabiliriz.

5- İlk Connectivity’den başlayacağız. Connectivity sekmesinden I2C1 sekmesine girip “default”da disable olan I2C seçeneğini I2C olarak ayarlayın. Ve Parameter Settings kısmından I2C speed Mode‘unu Fast Mode olarak seçiniz. 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.

6- ESP32 ve STM32 arasındaki uart bağlantısını kurmak için LPUART seçeneğini aktif etmemiz lazım. LPUART1 seçeneğini seçip Mode olarak Asynchronous modunu seçiniz. Baud Rate seçeneğini 115200 olarak belirleyiniz. Ve Pinout Viewdan PG8 ve PG7 pinlerini LPUART1_RX ve Tx olarak seçiniz.

7- Sırada GPIO ayarları yapılacaktır. Pinout View‘dan önce “Interrupt”larımızı ayarlayalım. Bir pin interrupt olarak seçmek için GPIO_EXTIx olarak seçmeniz gerekmektedir.

8- Pinin üstüne gelip sağ tıkladığımızda çıkan menüden Enter User Label ile istediğiniz bir isim yazabilirsiniz. PD15 boardumuzda LSM6dsl’nin ikinci interrupt pinidir.

9- Az önceki adımlarda yaptığımız şekilde diğer sensörlerin de interrupt pinlerini ayarlamalısınız. Aşağıdaki listede yer alan pinleri 7. ve 8. adımlarda gösterildiği gibi ayarlayabilirsiniz.

  • LSM6DSL Birinci Interrupt pini -> PF12
  • VL53L3 Interrupt pini -> PA6
  • LPS22 Interrupt pini -> PE9

10- Interrupt pinlerinizi ayarladıktan sonra “System Core” sekmesindeki GPIO kısmından NVIC “Interrupt”larınızı etkinleştirmelisiniz. Ayarladığımız pinlerin lineları otomatik olarak NVIC sekmesine gelecek, bunları etkinleştiriniz.

11- İnterruptlarımızı ayarladıktan sonra normal Output Pin ayarlarını yapabiliriz. Pinout Viewdan kullanacağımız pinleri istediğimiz pinlere sol tıklayarak GPIO_Output seçeneğini seçerek ayarlayabiliriz. GPIO pinlerinize User Label koymak kodunuzda portları bulmanızı kolaylaştıracaktır.

12- Az önceki adımlarda yaptığımız şekilde diğer GPIO Output pinlerini ayarlamalısınız. Aşağıdaki listede yer alan pinleri 11. adımda gösterildiği gibi ayarlayabilirsiniz.

  • 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 aşağısındaki arama barını kullanabilirsiniz.

14- SensNode Framework’ünü kodumuz ile oluşturmak için Software Packs seçeneğinin altından Select Components’a tıklayalım.

15- Açılan pencereden SensNode paketinin altındaki boş kutuyu tikleyiniz.

16- Ok tuşuna bastıktan sonra ana menünüzde sol tarafta yeni bir sekme açıldığını görüceksiniz. Software Packs sekmesi altından SensNode paketini seçip CMSIS Driver seçeneğini işaretleyiniz.

13- I2C, LPUART, Interrupt pinleri, Output pinleri ve SensNode paketini hazırladığımıza göre programımızı derleyebiliriz. Project Manager sekmesinden öncelikle Project Name kısmını dolduruyoruz. Sonrasında Toolchain/IDE olarak STM32CUBEIDE seçeneğini seçiyoruz. Son olarak Generate Code düğmesine basıyoruz.

Böylelikle sistemizin konfigürasyon dosyalarının da olduğu kodumuz generatelenmiştir. Sırada sensörlerin çalıştırılması vardır.

Sensörlerin Başlatılması ve Veri Alım Yöntemleri

SensNode üzerindeki sensörlerden veri çekmenin ve interruptların ayarlanması anlatılacaktır.

SHT30 Polling

Aşağıdaki kodu main.c dosyasının içindeki belirtilen yerlere kopyalayınız. Main.c dosyamız içerisinde User Codelarını yazabildiğimiz ve program “generator”un dokumandığı alanlar vardır. Bizde kodlarımızı bu gerekli yerlere yapıştıracağız.

Öncelikle header dosyalarımızı eklemek için /* USER CODE BEGIN Includes */ alanının altına yapıştıracağız



/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "sensError.h"
#include "sensEnv.h"
#include "sensMotion.h"
#include "vl53lx_api.h"
/* USER CODE END Includes */

Bu kodumuzda Pre-define kullanılıcaktır. #define ile gerekli yerlerin compilera girip girmeyeceğini ayarlayabileceğiz.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define SHT_POLL

/* USER CODE END PD */

Loglarımızı 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 */

SHT pollingini bir fonksiyon içerisinde yapacağız. Bu fonksiyonun prototipini gerekli yere yazalım.


/* USER CODE BEGIN PFP */

#ifdef SHT_POLL
void sht_polling_main(void);
#endif

/* USER CODE END PFP */

SHT30 fonksiyonu main içerisinde çağıracağımız yer “while”dan hemen öncedir.




int main(void) {

*
*
*


/* USER CODE BEGIN 2 */

#ifdef SHT_POLL
	sht_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 */
}

Son olarak fonksiyonumuzun kendisini yazabiliriz. Bunun için /* USER CODE BEGIN 4 */ bölgesini kullanacağız.




/* USER CODE BEGIN 4 */

#ifdef SHT_POLL
void sht_polling_main(void) {

	// Sens_Error for error handling
	Sens_Error_t err = SENS_FAILS;

	//Polling function require float data
	float temperature, humidity;

	//Initializing the SHT30
	err = SHTSensInit(&hi2c1);
	if (err != SENS_SUCCESS) {
		printf("Error when polling data. Error code:(%d)", err);
		Error_Handler();
	}

	//infinite loop
	while (1) {

		//Polling data
		err = read_SHT30(&hi2c1, &temperature, &humidity);
		if (err != SENS_SUCCESS) {
			printf("Error when polling data. Error code:(%d)", err);
		} else {
			printf("Temperature: %dC, Humidity:%d%RH\n\r ", (int) temperature,
					(int) humidity);
		}

		HAL_Delay(2000);
	}

}

#endif

/* USER CODE END 4 */


Bu fonksiyon içerisinde 2 saniyede bir sıcaklık ve nem verisi “poll”anıp ekrana basılması planlanmıştır.

SHT sensörünü initializelayabilmek için SHTSensInit(&hi2c1); fonksiyonunu çağırıyoruz. Datayı “Poll”amak için is read_SHT30(&hi2c1, &temperature, &humidity); kodunu kullanıyoruz.

STM32Cubeİde içerisinde üsteki taskbardan Run tuşuna basarak programı yükleyebilirsiniz.

Bir sonraki kod’a geçmeden önce #define SHT_POLL satırını yoruma alarak bu yaptıklarımızı derlemeden çıkarabiliriz.




/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

//#define SHT_POLL

/* USER CODE END PD */


LPS22df Poll

Bir önceki kodda “define” satırını yorumladığımız için aynı “main.c” dosyasını bir önceki kod ile karıştırmadan yazabiliriz. Öncellikle yeni “define” değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define LPS_POLL

/* USER CODE END PD */

Kullanacağımız fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */
#ifdef LPS_POLL
void lps_polling_main(void);
#endif
/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz “while”dan önce çağıralım.


int main(void) {
/* USER CODE BEGIN 2 */


*
*
*


#ifdef LPS_POLL
	lps_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 */
}

Şimdi sırada fonksiyonumuz oluşturalım.


/* USER CODE BEGIN 4 */

#ifdef LPS_POLL
void lps_polling_main(void) {
	// Sens_Error for error handling
	Sens_Error_t err = SENS_FAILS;

	int i;

	//Polling function require float data
	float temperature, pressure;

	//Initializing the LPS
	err = LpsSensInit(&hi2c1);
	if (err != SENS_SUCCESS) {
		Error_Handler();
	}

	//Setting the ODR for data polling
	LPS_Enable(&hi2c1);
	while (1) {
		err = read_LP22(&hi2c1, &temperature, &pressure);
		if (err != SENS_SUCCESS) {
			printf("Error when polling data. Error code:(%d)", err);
		} else {
			printf("Temperature: %dC, Pressure:%dhPa\n\r ", (int) temperature,
					(int) pressure);
		}

		HAL_Delay(2000);

	}

}

#endif

/* USER CODE END 4 */

Bu fonksiyonda 2 saniye aralıklar ile sıcaklık ve basınç verileri ekrana yazılması planlanmıştır.

LpsSensInit(&hi2c1);” ile sensörümüzü initialize ediyoruz. SHT aksine LPSnin ODR (sampling rate) ayarını yapabiliyoruz, bunu kullanarak sensörün veri almasını durdurabiliriz. Sensörün veri almasını açmak için LPS_Enable(&hi2c1); fonksiyonunu çağırıyoruz. Sıcaklık ve Basınç değerlerini okumak için read_LP22(&hi2c1, &temperature, &pressure); fonksiyonunu kullanabiliriz.

Bir önceki kodda yaptığımız gibi LPS_POLL “definen”ı yoruma alalım.




/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

//#define LPS_POLL

/* USER CODE END PD */


LSM6dsl Poll

Öncellikle yeni “define” değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define LSM_POLL

/* USER CODE END PD */

Kullanacağımız fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */

#ifdef LSM_POLL
void lsm_polling_main(void);
#endif

/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz “while”dan önce çağıralım.


int main(void) {


*
*
*


/* USER CODE BEGIN 2 */

#ifdef LSM_POLL
	lsm_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 */
}

Şimdi sırada fonksiyonumuz oluşturalım.


/* USER CODE BEGIN 4 */


#ifdef LSM_POLL
void lsm_polling_main(void) {

	// Sens_Error for error handling
	Sens_Error_t err = SENS_FAILS;

	//Initializing the LSM
	err = LSMSensInit(&hi2c1);
	if (err != SENS_SUCCESS) {
		Error_Handler();
	}

	uint8_t sw = 0;
	uint8_t cnt = 0;

        // Sensör verileri için ayrılan yer
	float acc[3] = { 0 };
	float gyro[3] = { 0 }; 

	while (1) {
		if (sw == 0) {
			if (cnt == 0) {
				printf("Accelerator Enabled\n\r");
                                // İvme senörünün ODRını açıyoruz
				ACC_Enable(&hi2c1);
			}
			read_acc(&hi2c1, acc);  // ivme verisinin okunması
			printf("AccX = %d, AccY = %d, AccZ = %d\n\r", (int) acc[0],
					(int) acc[1], (int) acc[2]);
			cnt++;
			if (cnt == 9) {
				printf("Accelerator Disabled\n\r");
				ACC_Disable(&hi2c1); // ivme odrını durduruyoruz


				cnt = 0;
				sw = 1;
			}
		}
		if (sw == 1) {
			if (cnt == 0) {
                                // gyro senörünün ODRını açıyoruz
				GYRO_Enable(&hi2c1);
				printf("Gyro Enabled\n\r");
			}
			read_gyro(&hi2c1, gyro);  // Gyro verisini okunması
			printf("GX = %d, GY = %d, GZ = %d\n\r", (int) gyro[0],
					(int) gyro[1], (int) gyro[2]);
			cnt++;
			if (cnt == 9) {
				printf("Gyro Disabled\n\r");
				GYRO_Disable(&hi2c1); // Gyro odrını durduruyoruz
				cnt = 0;
				sw = 0;
			}
		}
		HAL_Delay(1000);
	}
}

#endif



/* USER CODE END 4 */

Bu fonksiyonda 10 veride bir gyrometer ve accelerometer arasında togglelama yapılmıştır. LSMSensInit(&hi2c1); fonksiyonu ile sensörümüzü hazırlıyoruz. Accelerometer ve gyrometer “ODR”larını ayrı olarak setleyebiliyoruz. Bu iki sensörün veri almasını açmak için “Enable” komutlarını kullanıyoruz. Güç tasarrufu için “ODR”larını kapatabildiğimiz Disable fonksiyonları da bulunmaktadır. Sensörlerin verilerini okumak için read fonksiyonlarını çağırabilirsiniz.

ToF’a geçmeden önce LSM_POLL’un definelandığı yeri yorumlayınız

ToF Polling

Öncellikle yeni define değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */


#define TOF_POLL

/* USER CODE END PD */

Kullanacağımız fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */

#ifdef TOF_POLL
void tof_polling_main(void);
#endif

/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz “while”dan önce çağıralım.


int main(void) {


*
*
*


/* USER CODE BEGIN 2 */

#ifdef TOF_POLL
	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 */
}

Şimdi sırada fonksiyonumuz oluşturalım.


/* USER CODE BEGIN 4 */
#ifdef TOF_POLL
void tof_polling_main(void) {
	VL53LX_Dev_t dev;
	VL53LX_DEV Dev = &dev;
	VL53LX_MultiRangingData_t MultiRangingData;
	VL53LX_MultiRangingData_t *pMultiRangingData = &MultiRangingData;
	uint8_t NewDataReady = 0;
	int no_of_object_found = 0, j;
	int status;
	uint8_t byteData;
	uint16_t wordData;

	printf("VL53L1X Examples...\n\r");
	Dev->I2cHandle = &hi2c1;
	Dev->I2cDevAddr = 0x52;

	HAL_GPIO_WritePin(EN_VL53L3_GPIO_Port, EN_VL53L3_Pin, 1); // ToF sensörünün resetlenmesi
	HAL_Delay(10);
	HAL_GPIO_WritePin(EN_VL53L3_GPIO_Port, EN_VL53L3_Pin, 0); // ToF sensörünün resetlenmesi
	HAL_Delay(10);
	HAL_GPIO_WritePin(EN_VL53L3_GPIO_Port, EN_VL53L3_Pin, 1); // ToF sensörünün resetlenmesi
	HAL_Delay(10);

	VL53LX_RdByte(Dev, 0x010F, &byteData);  //Module id address
	printf("VL53LX Model_ID: %02X\n\r", byteData);
	VL53LX_RdByte(Dev, 0x0110, &byteData); // Module type address
	printf("VL53LX Module_Type: %02X\n\r", byteData);
	VL53LX_RdWord(Dev, 0x010F, &wordData);
	printf("VL53LX: %02X\n\r", wordData);

	status = VL53LX_WaitDeviceBooted(Dev);  // Sensörün boot olması bekleniyor
	status = VL53LX_DataInit(Dev);          // Sensör initialize ediliyor 
	status = VL53LX_StartMeasurement(Dev);  // ToF sensörü ölçümü başlatılır

	if (status) {
		printf("VL53LX_StartMeasurement failed: error = %d \n", status);
		while (1)
			;
	}

	while (1) {
		status = VL53LX_GetMeasurementDataReady(Dev, &NewDataReady); //Datayı çekilmesi için hazırlar
		HAL_Delay(1); // 1 ms polling period, could be longer.
		status = VL53LX_GetMultiRangingData(Dev, pMultiRangingData); // Veriyi çeker
		no_of_object_found = pMultiRangingData->NumberOfObjectsFound;
		printf("Count=%5d, ", pMultiRangingData->StreamCount);
		printf("#Objs=%1d ", no_of_object_found);
		for (j = 0; j < no_of_object_found; j++) {
			if (j != 0)
				printf("\n                     ");
			printf(
					// @suppress("Float formatting support")
					"status=%d, D=%5dmm, Signal=%d Mcps, Ambient=%d Mcps j: %d",// @suppress("Float formatting support")
					pMultiRangingData->RangeData[j].RangeStatus,
					pMultiRangingData->RangeData[j].RangeMilliMeter,
					pMultiRangingData->RangeData[j].ExtendedRange);
		}
		printf("\n");
		if (status == 0) {
			status = VL53LX_ClearInterruptAndStartMeasurement(Dev);
		}
		HAL_Delay(500);
	}
}

#endif


/* USER CODE END 4 */

Bu fonksiyonda uzaklık verisi ToF sensöründen alınıp ekrana basılıyordur. Sensör resetlendikten sonra bootlanması beklenmesi lazım, sonrasında VL53LX_DataInit(Dev) ile sensörün ayarları yapılır. VL53LX_StartMeasurement(Dev) fonksiyonu ile device ölçümlere başlar.

Datayı hazırlamak için öncelikle VL53LX_GetMeasurementDataReady(Dev, &NewDataReady) fonksiyonu kullanılır sonrasında dataready olunca VL53LX_GetMultiRangingData(Dev, pMultiRangingData) fonksiyonu ile veriler çekilir. Yeni bir ölçüm başlatmak için VL53LX_ClearInterruptAndStartMeasurement(Dev) fonksiyonu kullanılır.

Bir sonraki kod’a geçerken define’ı yoruma alınız.

LPS22df Fifo Interrupt

Öncellikle yeni “define” değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define LPS_INT

/* USER CODE END PD */

fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */

#ifdef LPS_INT
void lps_int_main(void);
#endif

/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz “while”dan önce çağıralım.


int main(void) {


*
*
*


/* USER CODE BEGIN 2 */

#ifdef LPS_INT
	lps_int_main();
#endif

/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
		/* USER CODE END WHILE */

		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
}

Fonksiyonumuzu bize ayrılan yerde yazabiliriz.


/* USER CODE BEGIN 4 */
#ifdef LPS_INT
void lps_int_main(void) {
	Sens_Error_t err = SENS_FAILS;
	float temperature, pressure;

	err = LpsSensInit(&hi2c1);
	if (err != SENS_SUCCESS) {
		Error_Handler();
	}

	LPS_Enable_int(&hi2c1);
	while(1){

	}
}

void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {

	lps22df_fifo_data_t data[5];
	int l = 0;
	if (GPIO_Pin == INT_LPS22_Pin) {
		LPS_fifo_read(&hi2c1, 5, data);
		printf("[\n\r");
		for (l = 0; l < 5; l++) {
			printf("pre %d : %dhPa \n\r", l, (int) (data[l].hpa));
		}
		printf("]\n\r");
	}
}
#endif

/* USER CODE END 4 */

LPS22df’nin bu örneğinde “watermark”ı 5 olan bir “fifo” yapılmıştır. Sistemin Interrupt üretmesi içinde watermark seviyesinin ulaşılması ayarlanmıştır. LPS_Enable_int(&hi2c1) ile fifo ve interruptları ayarlayabilirsiniz daha watermark seviyesini fonksiyon içerisinden değiştirebilirsiniz.

Sisteme bir interrupt geldiğinde sistem Callback fonksiyonlarını çağırır. Interruptı almamız için kullanacağım callback fonksiyonu yukarıdadır. Bu fonksiyon içerisinde basit bir if branchi ile Interruptın beklediğimiz yerden gelip, gelmediği kontrol edilebilir. LPS_fifo_read(&hi2c1, 5, data) fonksiyonu ile fifo içerisindeki veriler data variablının içerisine yazılır..

Bir sonraki kod’a geçerken define’ı yoruma alınız.

LSM6dsl Fifo Interrupt

Öncellikle yeni define değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define LSM_INT

/* USER CODE END PD */

fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */

#ifdef LSM_INT
void lsm_int_main(void);
#endif

/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz whiledan önce çağıralım


int main(void) {


*
*
*


/* USER CODE BEGIN 2 */

#ifdef LSM_INT
	lsm_int_main();
#endif

/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
		/* USER CODE END WHILE */

		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
}

Fonksiyonumuzu bize ayrılan yerde yazabiliriz.


/* USER CODE BEGIN 4 */
#ifdef LSM_INT

#define GYRO 0


uint8_t fifoReady = 0;

void lsm_int_main(void) {
	Sens_Error_t err = SENS_FAILS;
	fifoReady = 0;
	LSMSensInit(&hi2c1);
	err = LSM_Fifo_Enable(&hi2c1, GYRO);

	while(1){
		if(fifoReady){
			LSM_fifo_get_data(&hi2c1);

			fifoReady = 0;
		}
	}
}

void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {

	if (GPIO_Pin == INT1_LSM330_Pin) {
		fifoReady = 1;
	}
}

#endif

/* USER CODE END 4 */

Bu kodda GYRO verilerinin fifo ile seçilen watermak kadar doldurulup interrupt vermesi ayarlanmıştır. LSM_Fifo_Enable(&hi2c1, GYRO) ile GYRO verileri fifoya yazılır. Bu veriler LSM_fifo_get_data(&hi2c1) ile gyro verileri için olan değerler çekilir ve bastırılır.

Bir sonraki kod’a geçerken define’ı yoruma alınız.

LSM6dsl Single and Double Tap Interrupt

Öncellikle yeni define değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define LSM_Single_Double

/* USER CODE END PD */

fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */

#ifdef LSM_Single_Double
void lsm_single_double(void);
#endif

/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz whiledan önce çağıralım.


int main(void) {


*
*
*


/* USER CODE BEGIN 2 */

#ifdef LSM_Single_Double
	lsm_single_double();
#endif

/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
		/* USER CODE END WHILE */

		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
}

Fonksiyonumuzu bize ayrılan yerde yazabiliriz.


/* USER CODE BEGIN 4 */
#ifdef LSM_Single_Double

void lsm_single_double(void) {
	Sens_Error_t err = SENS_FAILS;
	err = LSMSensInit(&hi2c1);

	LSM_Enable_Single_Tap_Detection(&hi2c1, LSM6DSL_INT1_PIN);
	LSM_Enable_Double_Tap_Detection(&hi2c1, LSM6DSL_INT2_PIN);
	while(1){

	}
}

void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {

	if (GPIO_Pin == INT1_LSM330_Pin) {
		printf("Single Tap\n\r");
	}

	if (GPIO_Pin == INT2_LSM330_Pin) {
		printf("Double Tap\n\r");
	}
}

#endif

/* USER CODE END 4 */

Bu fonksiyonda iki farklı interrupt hazırlanmıştır. Single ve Double olarak ayrılan bu iki interrupt LSM6dslnin iki ayrı interrupt pininden geliyor. Detectionlar açıldıktan sonra Callback fonksiyonunda hangi pinden geldiğine bakılarak interrupt türü ayırt edilebilir.

Bir sonraki kod’a geçerken define’ı yoruma alınız.

LSM6dsl 6D Orientation with Interrupt

Öncellikle yeni define değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define LSM_6D_OR

/* USER CODE END PD */

Fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */

#ifdef LSM_6D_OR
void lsm_6d_orientation_main(void);
#endif

/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz whiledan önce çağıralım.


int main(void) {


*
*
*


/* USER CODE BEGIN 2 */

#ifdef LSM_6D_OR
	lsm_6d_orientation_main();
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
		/* USER CODE END WHILE */

		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
}

Fonksiyonumuzu bize ayrılan yerde yazabiliriz.


/* USER CODE BEGIN 4 */
#ifdef LSM_6D_OR

uint8_t fifoReady = 0;

void lsm_6d_orientation_main(void) {
	Sens_Error_t err = SENS_FAILS;
	err = LSMSensInit(&hi2c1);
	LSM_6D_Oriented_Enable(&hi2c1, LSM6DSL_INT1_PIN);
	uint8_t xh,xl,yh,yl,zh,zl;
	fifoReady = 0;
	while (1) {
		if (fifoReady) {
			LSM_6D_Get_XL(&hi2c1, &xl);
			LSM_6D_Get_XH(&hi2c1, &xh);
			LSM_6D_Get_YL(&hi2c1, &yl);
			LSM_6D_Get_YH(&hi2c1, &yh);
			LSM_6D_Get_ZL(&hi2c1, &zl);
			LSM_6D_Get_ZH(&hi2c1, &zh);
			HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, yl);
			HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, xh);
			HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, yh);
			HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, xl);
			printf("xh : %d , xl: %d , yh: %d , yl : %d , zh : %d , zl : %d \n\r", xh, xl, yh, yl, zh, zl);
			fifoReady = 0;
		}
	}
}

void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) {

	if (GPIO_Pin == INT1_LSM330_Pin) {
		fifoReady = 1;
	}
}

#endif

/* USER CODE END 4 */

Bu fonksiyonda boardumuzun 45 derece bir açı değişikliği yaptığında bir interrupt vermesi sağlanıp eğim yönüne göre ledlere basılması sağlanmıştır. LSM_6D_Oriented_Enable(&hi2c1, LSM6DSL_INT1_PIN) ile interrupt 1. interrupt pini üzerinden verilir. interrupt geldiği vakit 6 farklı eğim get komutlarıyla bulunabilir.

Bir sonraki kod’a geçerken define’ı yoruma alınız.

Time Of Flight İnterrupt

Öncellikle yeni define değerimiz ekleyelim.


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define TOF_INT

/* USER CODE END PD */

Fonksiyonun prototipini gerekli yere yazalım.


/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN PFP */

#ifdef TOF_INT
void tof_int_main(void);
#endif

/* USER CODE END PFP */

Main fonksiyonu içerisinde fonksiyonumuz whiledan önce çağıralım.


int main(void) {


*
*
*


/* USER CODE BEGIN 2 */

#ifdef TOF_INT
	tof_int_main();
#endif
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1) {
		/* USER CODE END WHILE */

		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
}

Fonksiyonumuzu bize ayrılan yerde yazabiliriz.


/* USER CODE BEGIN 4 */
#ifdef TOF_INT
volatile int IntCount;

void tof_int_main(void) {
	VL53LX_Dev_t dev;
	VL53LX_DEV Dev = &dev;
	VL53LX_MultiRangingData_t MultiRangingData;
	VL53LX_MultiRangingData_t *pMultiRangingData = &MultiRangingData;
	uint8_t NewDataReady = 0;
	int no_of_object_found = 0, j;
	int status;
	IntCount = 0;
	uint8_t byteData;
	uint16_t wordData;

	printf("VL53L1X Examples...\n\r");
	Dev->I2cHandle = &hi2c1;
	Dev->I2cDevAddr = 0x52;

	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);

	VL53LX_RdByte(Dev, 0x010F, &byteData);
	printf("VL53LX Model_ID: %02X\n\r", byteData);
	VL53LX_RdByte(Dev, 0x0110, &byteData);
	printf("VL53LX Module_Type: %02X\n\r", byteData);
	VL53LX_RdWord(Dev, 0x010F, &wordData);
	printf("VL53LX: %02X\n\r", wordData);

	status = VL53LX_WaitDeviceBooted(Dev);
	status = VL53LX_DataInit(Dev);
	status = VL53LX_StartMeasurement(Dev);


	if (status) {
		printf("VL53LX_StartMeasurement failed: error = %d \n", status);
		while (1)
			;
	}
	status = VL53LX_ClearInterruptAndStartMeasurement(Dev);
	while (1) {
		__WFI();
		if (IntCount != 0) {
			IntCount = 0;
			status = VL53LX_GetMultiRangingData(Dev, pMultiRangingData);
			no_of_object_found = pMultiRangingData->NumberOfObjectsFound;
			printf("Count=%5d, ", pMultiRangingData->StreamCount);
			printf("#Objs=%1d ", no_of_object_found);
			for (j = 0; j < no_of_object_found; j++) {
				if (j != 0)
					printf("\n                     ");
				printf( // @suppress("Float formatting support")
						"status=%d, D=%5dmm, Signal=%2.2f Mcps, Ambient=%2.2f Mcps", // @suppress("Float formatting support")
						pMultiRangingData->RangeData[j].RangeStatus,
						pMultiRangingData->RangeData[j].RangeMilliMeter,
						pMultiRangingData->RangeData[j].SignalRateRtnMegaCps
								/ 65536.0,
						pMultiRangingData->RangeData[j].AmbientRateRtnMegaCps
								/ 65536.0);
			}
			printf("\n");
			if (status == 0) {
				HAL_Delay(100);
				status = VL53LX_ClearInterruptAndStartMeasurement(Dev);
			}

		}
	}

}


void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin){
	if (GPIO_Pin == INT_VL53L3_Pin) {
		IntCount = 1;
	}
}
#endif

/* USER CODE END 4 */

Time of flight sensörü her data hazır olduğunda bir interrupt vererek haber veririr. Tof Polling ve bu interrupt kodunun arasındaki farklardan birisi dataready fonksiyonu gerekmez ve while loop’a girmeden önce interruptlar yenilenir.

SensNode üzerindeki belli interruptlar ve pollamalar gösterilmiştir. Sırada ThreadX’de basit bir program nasıl kurulacağı anlatılacaktır.

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 iocyi STM32CubeMX .ioc file bölgesine giriniz. Yeni dosyanızın ismini belirledikten sonra Finish tuşuna basınız.

Karşınıza IOC ekranı gelince yapmanız gereken Middleware -> THREADX seçeneğine gidiniz. Sonra core seçeneğini aktif edin. 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 istiyor, bu yüzden TIM6 timerını Timebase source olarak kullanabilirsiniz.

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ı generatelemek için (ctrl + S) yapınız.

ThreadX File ayarlanması

Bu uygulamada yapmak istediğimiz iki ledin farklı threadler ile kontrol edilmesi ve farklı loglar vermesi yapılacaktır.

Öncelikle app_thread.c dosyasına gidelim. Pinleri ve APP Pool sizeı çekebilmek için aşağıdaki iki header dosyasını ekleyelim.


/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include "app_azure_rtos_config.h"
#include "main.h"

/* USER CODE END Includes */

Definelarımız ayarlamak için app_thread.h dosyasına gidelim. Aşağıdaki defineları ekleyelim.


/* 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. Threadlerimizin memorylerini allocateleyeceğimiz ve başlatacağımız yer bu fonksiyonun içerisidir. Öncesinde Threadlerin variablelarını ve entry pointlerini global da tanımlayalım.


/* 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 */

Şimdi threadleri oluşturabiliriz.


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 allocatelenir. Sonrasında threadi pointerın olduğu stackte başlatılır. Aşağıdaki figurde gösterilen pointer stacklerin giriş ve çıkışlarını tutmak içindir.

Threadlerin entry pointlerinin prototiplerini yukarıda yazmıştık şimdi fonksiyonlarını oluşturalım.


/* 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 eklentiler yapılması lazım. 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 STM’e yükleyeniz.

Program Çalıştıktan Sonra board üzerinde Mavi ve Yeşil ledler yanıcaktır.

  • Mavi = 1 , Yeşil = 0 -> ESP internete bağlanmak için provision 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 -> ESP Amazona bağlandı ve datalar için hazır.

ESP32 amazona bağlandıktan sonra cihazınızın verilerini aşağıdaki linkten kontrol edebilirsiniz.

Dökümantasyonun devamı Workshop 2’de gelecektir.