Selam Makerlar,
Bu yazımızda, DemeduKit ile Accelerometer(ivmeölçer), OLED, NTC gibi temel bileşenlerin kontrolünü nasıl gerçekleştirebileceğinizi adım adım anlatacağız. Hazırsanız, projeye birlikte adım atalım!
Gerekli Malzemeler:
Aşamalar
- Proje yapımı hakkında
- Gerekli kütüphaneler
- Pin bağlantıları
- Kod yazım aşaması
- Sonuçlar
Proje Yapımı Hakkında
STM32 mikrodenetleyicisi ile ivme ve sıcaklık verilerini sürekli olarak okuyarak Oled ekranda gösteren, aynı zamanda farklı led’leri ve buzzer’ı kontrol eden bir yapılandırma oluşturulmuştur. Bu işlemler, belirli zaman dilimlerinde tekrarlanır ve bu sayede kullanıcıya gerçek zamanlı bir veri izleme deneyimi sunulur.
NTC (Negative Temperature Coefficient): Sıcaklık sensörleri, sıcaklık değiştikçe direnç değeri değişen pasif elemanlardır. Sıcaklık arttıkça NTC’nin direnci düşer, sıcaklık azaldıkça direnci artar. Bu direnç değişimini ölçmek ve sıcaklık verisine dönüştürmek için analog bir yöntem gereklidir. ADC (Analog-to-Digital Converter), analog bir sinyali dijital bir değere dönüştüren birimdir. Bu nedenle, NTC sensöründen sıcaklık verisi okumak için ADC kullanılır.
Oled (Organic Light Emitting Diode): Organik bileşiklerden yapılan, kendi ışığını üreten bir ekran teknolojisidir. Oled için I2C bağlantı protokolünü kullanalım. I2C iki hat üzerinden haberleşme sağlar;
- SDA (Serial Data Line): Veri hattı.
- SCL (Serial Clock Line): Saat hattı, veri iletim hızını belirler.
BUZZER: Genel olarak ses üretici bir elemandır ve elektronik projelerde çeşitli amaçlarla kullanılır. İki temel buzzer türü vardır pasif ve aktif olmak üzere; pasif buzzer bir sinüs dalgası (AC) uygulandığında ses üretir. Aktif buzzer ise Dahili bir osilatör içerir, bu yüzden belirli bir frekansta çalışır. Sadece DC voltaj uygulandığında ses üretir.
İVMEÖLÇER (Accelerometer): Hareketi ve yer çekimi kuvvetini ölçen bir sensördür. Genellikle üç eksende (X, Y, Z) hareketi ölçebilir ve bu veriler, cihazın eğimini, hızını veya ivmesini belirlemek için kullanılabilir. İvmeölçer bağlantısı için I2c protokolünü kullanalım.
Gerekli Kütüphaneler
DemsayLib kütüphanesi, STM32 mikrodenetleyiciler için geliştirilmiş bir kütüphanedir ve çeşitli periferik bileşenler için driver’lar, zamanlayıcı ve kesme yönetimi, kullanıcı dostu API’ler sağlar. Bu, geliştirme sürecini hızlandırır, verimliliği artırır ve yeni başlayanlara eğitim ve öğrenme desteği sunar. Kütüphanenin sağladığı örnek kodlar ve belgeler, STM32 mikrodenetleyicileri ile uygulama geliştirmeyi daha erişilebilir ve verimli hale getirir.
SSD1306 kütüphanesi, STM32 mikrodenetleyicileri kullanarak SSD1306 OLED ekran modülünü kontrol etmek için geliştirilmiş bir yazılım kütüphanesidir. Ekranın başlatılması, yapılandırılması, görüntü ve metin çizilmesi gibi işlemleri kolaylaştırır. Kütüphane, geliştiricilere ekran ile etkileşimde bulunurken büyük bir kolaylık sağlar ve ekranın doğru şekilde çalışmasını sağlar.
LIS2DW12 kütüphanesi, STM32 mikrodenetleyicileri ile LIS2DW12 3 eksenli ivmeölçer sensörünü kontrol etmek için geliştirilmiş bir kütüphanedir. Sensörün başlatılması, yapılandırılması, ivme verilerinin okunması ve kesme yönetimi gibi işlemleri kolaylaştırır. Kütüphane, sensörle etkileşimi basit ve kullanıcı dostu hale getirir, zaman kazandırır ve enerji verimliliği sağlar.
Proje için indirilmesi gereken kütüphaneler DemsayLib, lis2dw12, ssd1306 bu kütüphanelerin zip dosyalarını indirip projenin olduğu klasör içerisine ayıklayalım. Daha sonrasında CubeIDE programında projeyi yenilediğimizde orada kütüphaneler gözükecektir. Bu kütüphaneleri sayfa içerisinde tek tek aktifleştirelim.
Kütüphaneleri aktifleştirmek için projeye sağ tıkla- properties- C/C++ General- Paths and Symbols – GNU C içeriklerini ve kaynak yerlerinii ekleyelim:
Pin Bağlantıları
Demedukit kartındaki STM32G030 mikrodenetleyicisindeki konfigürasyona göre pin bağlantılarını oluşturalım.
- İlk olarak led, buzzer ve rgb led çıkış pinlerini çıkışa bağlayıp isimlendirelim:
- NTC bağlantısı ADC olarak bağlayalım
- Oled ve İvmeölçer için I2C protokolünü aktifleştirelim. I2C protokolü için iki hat haberleşme sağlıyordu ikisini de aktifleştirelim. İlk olarak aşağıdaki gibi pb6 ve pb7’de pinleri seçelim daha sonrasında bağlantıyı aktifleştirelim:
- SYS terimi genellikle sistem kontrol ve yapılandırma ile ilgili fonksiyonları ifade eder. Bu protokolü de aktifleştirelim:
- Oled ekran, ivmeölçer ve NTC termistör gibi bileşenlerin doğru ve verimli bir şekilde çalışabilmesi için RCC kullanımı gereklidir. RCC, mikrodenetleyicinin I2C, SPI ve ADC modüllerine gerekli saat sinyallerini sağlar ve bu modüllerin doğru çalışmasını destekler. Bu, sistemin genel güvenilirliğini ve performansını artırır. Yüksek hızda saat sinyalini kristal seramik olarak seçip aktifleştirelim ve NVIC ayarlar kısmından etkinleştirelim:
- RTC’yi aktifleştirelim:
Kod Yazım Aşaması
1.İlk olarak ‘main.h’ dosyasına girelim ve burada projemizin işleyiş düzeni farklı zaman aralıklarında olacağı için bir yapı tanımlayalım farklı birkaç zaman aralığını girelim.
/* USER CODE BEGIN Private defines */
typedef struct
{
uint8_t _1msn;
uint8_t _10msn;
uint8_t _50msn;
uint8_t _100msn;
uint8_t _250msn;
uint8_t _500msn;
uint8_t _1sn;
uint8_t _2sn;
uint8_t _5sn;
}SystemClockTimer_t;
SystemClockTimer_t SysClkTim;
/* USER CODE END Private defines */
- SystemClockTimer_t yapısı, çeşitli zaman aralıklarını temsil eden üyeleri içeren bir yapı tanımlar.
- SysClkTim değişkeni, bu yapıyı kullanarak farklı zaman aralıklarını depolayabilir ve yönetebilir.
2.Belirli zaman aralıklarıyla işlemler gerçekleşeceği için RTC (Real-Time Clock) modülünü aktifleştirelim bunun için ‘stm32g0xx_hal_conf.h’ dosyasına girelim ve aşağıdaki modülü aktif hale getirelim.
#define HAL_RTC_MODULE_ENABLED
3.Başlık dosyalarını programa dahil edelim ve daha sonrasında sıcaklık sensöründen gelen analog değeri alıp, bu değeri termistörün direnci ve ardından sıcaklık cinsine dönüştüren kodu yazalım.
/* USER CODE BEGIN Includes */
#include "lis2dw12_reg.h"
#include "ssd1306.h"
#include "stdio.h"
#include "string.h"
#include "ssd1306_tests.h"
#include "DigitalInputOutputs.h"
double Termistor(uint32_t analogValue)
{
double temperature;
uint32_t adcval = 4096 - analogValue;
temperature = log((adcval * 10000) / (4095 - adcval));
temperature = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * temperature * temperature)) * temperature);
temperature = temperature - 273.15;
return temperature;
}
/* USER CODE END Includes */
4. Döngüde kullanılacak sayaç ve rgb sayacı 0’dan başlatalım daha sonrasında sıcaklık değerini saklayacak Temp1 değişkenini de ondalıklı sayı olarak ekleyelim.
/* USER CODE BEGIN PTD */
uint8_t counter = 0;
uint8_t rgbcounter = 0;
double Temp1;
/* USER CODE END PTD */
5. Kodun okunabilirliğini arttırmak ve hata riskini azaltmak için aşağıdaki tanımlamaların yapılması gerek.
Projede bir sensörle I2C protokolü üzerinden iletişim kurmak için hangi I2C arayüzünün kullanılacağını belirten bir tanımlama eklenir. Örneğin, sensörden veri okuma veya sensöre veri gönderme işlemleri SENSOR_BUS kullanılarak yapılacaktır, ki bu da hi2c1 üzerinden gerçekleşecektir.
Bir cihazın veya sensörün başlatılması (boot) sırasında gereken bekleme süresini ifade etmek için BOOT_TIME kullanılır.
/* USER CODE BEGIN PM */
#define SENSOR_BUS hi2c1
#define BOOT_TIME 20 //ms
/* USER CODE END PM */
6.
/* USER CODE BEGIN PV */
volatile uint64_t SYSTickTimer = 0; //Sistemin zamanlayıcısının (SysTick) sayaç değeri için kullanılır. Zamanlayıcı tabanlı olayları izlemek için kullanılır.
stmdev_ctx_t dev_ctx; // Bir sensör veya cihazla ilgili işlemleri yönetmek için kullanılan bir yapı.
static int16_t data_raw_acceleration[3]; // İvmeölçerden gelen ham ivme verilerini tutar.
static float acceleration_mg[3]; //Ham ivme verilerini milig cinsine çevirip saklar
static uint8_t whoamI, rst; //Bir sensörün veya cihazın sıfırlama durumunu izlemek için kullanılır.
/* USER CODE END PV */
7.
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_I2C1_Init(void);
static void MX_RTC_Init(void);
/* USER CODE BEGIN PFP */
8. Mikrodenetleyicinin donanımla doğru şekilde iletişim kurmasını sağlayan temel giriş/çıkış işlemlerini temsil etmek için aşağıdaki kod kullanılır.
/* USER CODE BEGIN PFP */
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp, uint16_t len);
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len);
/* USER CODE END PFP */
- handle: Yine, cihazın hangi arabirim üzerinden iletişim kuracağını belirten bir işaretçidir.
- reg: Okuma işleminin yapılacağı register adresini belirtir.
- bufp: Okunan verinin saklanacağı bellek alanını işaret eder.
- len: Okunacak veri uzunluğunu (byte cinsinden) belirtir.
9. Veri iletişimi, ADC ile ölçüm yapma ve hata kontrolü gibi işlemleri gerçekleştirmek için kullanılan temel değişkenleri tanımlayalım. HAL fonksiyonlarıyla donanım işlemlerini yönetmek, ADC’den analog verileri dijital formda almak ve bu verileri bir tamponda depolamak gibi görevler için bu değişkenler kullanılır.
/* USER CODE BEGIN 0 */
HAL_StatusTypeDef ret = HAL_ERROR;//ret değişkeni, HAL fonksiyonlarının sonuçlarını kontrol etmek için kullanılır.
uint8_t rec_buf[10] =
{ 0 }; //rec_buf, veri almak veya iletmek için kullanılan bir tamponu temsil eder.
volatile uint32_t adcValues[10]; //adcValues, ADC'den alınan verileri saklamak için kullanılan bir dizi olup, volatile anahtar kelimesi ile donanım tarafından değişebileceğini belirtir.
/* USER CODE END 0 */
10. Buzzer ve led kontrollerini yapalım.
/* USER CODE BEGIN 2 */
ssd1306_TestDrawBitmap();//Ekranın doğru şekilde çalıştığını test etmek için bir bitmap veya test görüntüsü çizer.
ssd1306_Init();// OLED ekran modülünü başlatır ve gerekli yapılandırmaları yapar.
Buzzer_Control(1);
HAL_Delay(200);
Buzzer_Control(0);
HAL_Delay(200);
ssd1306_SetCursor(15, 2);
ssd1306_WriteString("DEMSAY", Font_16x26, White);
ssd1306_SetCursor(14, 27);
ssd1306_WriteString("EDUCATION", Font_11x18, White);
ssd1306_SetCursor(45, 46);
ssd1306_WriteString("KIT", Font_11x18, White);
ssd1306_UpdateScreen();
HAL_Delay(3000);
ssd1306_Fill(Black);
RGB_LED_Control(1, 0, 0);
LED_Control(1, 0, 0, 0, 0);
HAL_Delay(200);
RGB_LED_Control(0, 1, 0);
LED_Control(0, 1, 0, 0, 0);
HAL_Delay(200);
RGB_LED_Control(0, 0, 1);
LED_Control(0, 1, 0, 0, 0);
HAL_Delay(200);
RGB_LED_Control(1, 0, 1);
LED_Control(0, 0, 1, 0, 0);
HAL_Delay(200);
RGB_LED_Control(1, 1, 0);
LED_Control(0, 0, 0, 1, 0);
HAL_Delay(200);
RGB_LED_Control(1, 1, 1);
LED_Control(0, 0, 0, 0, 1);
HAL_Delay(200);
RGB_LED_Control(0, 0, 0);
LED_Control(1, 1, 1, 1, 1);
HAL_Delay(200);
LED_Control(0, 0, 0, 0, 0);
Buzzer_Control(1);
HAL_Delay(200);
Buzzer_Control(0);
HAL_Delay(200);
11. İvmeölçer sensör ayarları yapalım.
dev_ctx.write_reg = platform_write;
dev_ctx.read_reg = platform_read;
dev_ctx.handle = &SENSOR_BUS;
lis2dw12_device_id_get(&dev_ctx, &whoamI);
lis2dw12_reset_set(&dev_ctx, PROPERTY_ENABLE);
do
{
lis2dw12_reset_get(&dev_ctx, &rst);
} while (rst);
/* Enable Block Data Update */
lis2dw12_block_data_update_set(&dev_ctx, PROPERTY_ENABLE);
/* Set full scale */
lis2dw12_full_scale_set(&dev_ctx, LIS2DW12_2g);
/* Configure filtering chain
* Accelerometer - filter path / bandwidth
*/
lis2dw12_filter_path_set(&dev_ctx, LIS2DW12_LPF_ON_OUT);
lis2dw12_filter_bandwidth_set(&dev_ctx, LIS2DW12_ODR_DIV_4);
/* Configure power mode */
lis2dw12_power_mode_set(&dev_ctx, LIS2DW12_HIGH_PERFORMANCE);
/* Set Output Data Rate */
lis2dw12_data_rate_set(&dev_ctx, LIS2DW12_XL_ODR_25Hz);
/* USER CODE END 2 */
12. Mikrodenetleyici tabanlı gömülü sistemin temel işlevlerini yerine getirmek için sürekli döngüye alalım. Aşağıdaki kod genel olarak İvmeölçer ve sıcaklık sensöründen veri alır, bu verileri işler ve OLED ekranda gösterir. Ayrıca, LED’lerin ve RGB LED’in belirli aralıklarla yanıp sönmesini sağlar.
/* USER CODE BEGIN WHILE */
while (1)
{
uint8_t reg;
/* Read output only if new value is available */
lis2dw12_flag_data_ready_get(&dev_ctx, ®);
if (reg)
{
/* Read acceleration data */
memset(data_raw_acceleration, 0, sizeof(data_raw_acceleration));
lis2dw12_acceleration_raw_get(&dev_ctx, data_raw_acceleration); //reading values from the accelerometer
acceleration_mg[0] = lis2dw12_from_fs2_to_mg(data_raw_acceleration[0]);
acceleration_mg[1] = lis2dw12_from_fs2_to_mg(data_raw_acceleration[1]);
acceleration_mg[2] = lis2dw12_from_fs2_to_mg(data_raw_acceleration[2]);
}
if (SysClkTim._1sn == 1)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 50); //reading values from the Temperature sensor with ADC
adcValues[2] = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
Temp1 = Termistor(adcValues[2]) - 10;
HAL_GPIO_TogglePin(LED1_RED_GPIO_Port, LED1_RED_Pin);
SysClkTim._1sn = 0;
}
if (SysClkTim._50msn == 1)
{
HAL_GPIO_TogglePin(LED2_GREEN_GPIO_Port, LED2_GREEN_Pin);
SysClkTim._50msn = 0;
}
if (SysClkTim._100msn == 1)
{
char str[10] =
{ 0 };
char Tmp[10] =
{ 0 };
sprintf(str, "%.2f m/s2", acceleration_mg[2] / 100);
HAL_GPIO_TogglePin(LED3_BLUE_GPIO_Port, LED3_BLUE_Pin);
ssd1306_SetCursor(2, 0);
ssd1306_WriteString("G:", Font_11x18, White);
ssd1306_SetCursor(25, 0);
ssd1306_WriteString(str, Font_11x18, White); // printing the values from the accelerometer
ssd1306_UpdateScreen();
sprintf(Tmp, "%.2fC", Temp1);
ssd1306_SetCursor(2, 25);
ssd1306_WriteString("Temp:", Font_11x18, White);
ssd1306_SetCursor(55, 25);
ssd1306_WriteString(Tmp, Font_11x18, White); // printing the values from the Temperature Sensor (NTC)
SysClkTim._100msn = 0;
}
if (SysClkTim._250msn == 1)
{
HAL_GPIO_TogglePin(LED4_WHITE_GPIO_Port, LED4_WHITE_Pin);
SysClkTim._250msn = 0;
}
if (SysClkTim._500msn == 1)
{
counter++;
if (counter % 2 == 1)
HAL_GPIO_WritePin(LED5_YELLOW_GPIO_Port, LED5_YELLOW_Pin, 1);
else
HAL_GPIO_WritePin(LED5_YELLOW_GPIO_Port, LED5_YELLOW_Pin, 0);
if (counter > 2)
{
counter = 0;
}
SysClkTim._500msn = 0;
}
if (SysClkTim._2sn == 1)
{
rgbcounter++;
switch (rgbcounter)
{
case 0:
HAL_GPIO_WritePin(RGB_R_GPIO_Port, RGB_R_Pin, 1);
HAL_GPIO_WritePin(RGB_G_GPIO_Port, RGB_G_Pin, 0);
HAL_GPIO_WritePin(RGB_B_GPIO_Port, RGB_B_Pin, 0);
break;
case 1:
HAL_GPIO_WritePin(RGB_R_GPIO_Port, RGB_R_Pin, 0);
HAL_GPIO_WritePin(RGB_G_GPIO_Port, RGB_G_Pin, 1);
HAL_GPIO_WritePin(RGB_B_GPIO_Port, RGB_B_Pin, 0);
break;
case 2:
HAL_GPIO_WritePin(RGB_R_GPIO_Port, RGB_R_Pin, 0);
HAL_GPIO_WritePin(RGB_G_GPIO_Port, RGB_G_Pin, 0);
HAL_GPIO_WritePin(RGB_B_GPIO_Port, RGB_B_Pin, 1);
break;
case 3:
HAL_GPIO_WritePin(RGB_R_GPIO_Port, RGB_R_Pin, 1);
HAL_GPIO_WritePin(RGB_G_GPIO_Port, RGB_G_Pin, 1);
HAL_GPIO_WritePin(RGB_B_GPIO_Port, RGB_B_Pin, 1);
break;
case 4:
HAL_GPIO_WritePin(RGB_R_GPIO_Port, RGB_R_Pin, 0);
HAL_GPIO_WritePin(RGB_G_GPIO_Port, RGB_G_Pin, 0);
HAL_GPIO_WritePin(RGB_B_GPIO_Port, RGB_B_Pin, 0);
break;
}
if (rgbcounter > 4)
{
rgbcounter = 0;
}
SysClkTim._2sn = 0;
}
/* USER CODE END WHILE */
13. STM32 mikrodenetleyicisi ile bir LIS2DW12 ivmeölçer sensörü arasında I2C haberleşmesi yapmak ve sistem zamanlayıcısını (SysTick) kullanarak çeşitli zamanlama olaylarını yönetmek için aşağıdaki kodu yazalım.
/* USER CODE BEGIN 4 */
// SysTick Zamanlayıcı Geri Çağırma Fonksiyonu
void HAL_SYSTICK_Callback(void)
{
SYSTickTimer++;
if (SYSTickTimer % 1 == 0)
{
SysClkTim._1msn = 1;
}
if (SYSTickTimer % 10 == 0)
{
SysClkTim._10msn = 1;
}
if (SYSTickTimer % 50 == 0)
{
SysClkTim._50msn = 1;
}
if (SYSTickTimer % 100 == 0)
{
SysClkTim._100msn = 1;
}
if (SYSTickTimer % 250 == 0)
{
SysClkTim._250msn = 1;
}
if (SYSTickTimer % 500 == 0)
{
SysClkTim._500msn = 1;
}
if (SYSTickTimer % 1000 == 0)
{
SysClkTim._1sn = 1;
}
if (SYSTickTimer % 2000 == 0)
{
SysClkTim._2sn = 1;
}
if (SYSTickTimer % 5000 == 0)
{
SysClkTim._5sn = 1;
}
}
//platform_write ve platform_read Fonksiyonları
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp, uint16_t len)
{
HAL_I2C_Mem_Write(handle, LIS2DW12_I2C_ADD_H, reg,
I2C_MEMADD_SIZE_8BIT, (uint8_t*) bufp, len, 1000);
return 0;
}
/*
* @brief Read generic device register (platform dependent)
*
* @param handle customizable argument. In this examples is used in
* order to select the correct sensor bus handler.
* @param reg register to read
* @param bufp pointer to buffer that store the data read
* @param len number of consecutive register to read
*
*/static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len)
{
HAL_I2C_Mem_Read(handle, LIS2DW12_I2C_ADD_H, reg,
I2C_MEMADD_SIZE_8BIT, bufp, len, 1000);
return 0;
}
/* USER CODE END 4 */2
14. HAL kütüphanesinin zamanlayıcı ile ilgili işlevlerinin düzgün çalışmasını sağlamak için ‘stm32g0xx_it.c’ dosyasına aşağıdaki kodu ekleyelim.
/* USER CODE BEGIN SysTick_IRQn 1 */
HAL_SYSTICK_IRQHandler();
/* USER CODE END SysTick_IRQn 1 */
15. Bu kodda sprintf fonksiyonu kullanılarak, float türündeki değerler formatlanıp bir karakter dizisine yazdırılıyor ve ardından bu diziler oled ekranına yazdırılıyor. Float türündeki değerlerin formatlanması ve yazdırılması için ‘-u _printf_float’ bayrağını eklemeniz gerekiyor.
16. Bir değişkenin birden fazla dosyada tanımlanmasını kaldırmak için proje sağ tık – properties- C/C++ Build – settings – MCU MPU GCC COMPİLER – Miscellaneous buraya ‘–fcommon’ bayrağını ekleyelim.