Elektronik

STM32 Uzaktan Gömülü Yazılım Güncelleme


Merhabalar, bu içeriği bootloader serisinin son yazısı olarak düşünebiliriz. Şayet direkt olarak bu yazıyla karşılaştıysanız ancak konu kümülatif bir şekilde ilerlediği için, şuradan diğer içerikleri incelemeniz sizin için faydalı olacaktır. Daha önce Duru isminde bir non-blocking, kullanımı rahat ve farklı MCU ailelerinden üyelerine port edilmesi mümkün ve rahat olan bir framework çalışmamı yayınlamıştım. Bugün ki uygulamamız yine o yapı üzerinde çalışacaktır.

Ne yapacağımızı daha rahat anlamak adına aşağıda bulunan görseli oluşturdum. Görsel Modemin (UC20 QUECTEL) ayağa kaldırılması gibi süreçleri içermiyor. Periyodik olarak backend tarafında yeni bir yazılım olup olmadığını sorguladığımız kısım özelinden başlıyor.

Belirlediğim formata göre sunucu üzerinde dosya şu şekilde barınmakta.

 can_%u.%u.%u.bin 
      |  | +---- bugfix-id
      |  + ----- minor-id
      + -------- major-id

Dosyanın hazırlanma sürecinde derleyici üzerinde yapılması gerekenlerden daha önce bahsetmiştim. Şimdi ise dosyanın içeriğinden bahsedeyim. ‘projeadı.bin’ dosyası oluştuktan sonra bu dosyayı Hex Editor programı üzerinden açarak dosyanın CRC32’sini buradan hesaplayarak ekliyorum ve ardından yayına verilecek versiyon damgasını 4 byte şeklinde ekliyorum.

Şimdi yazılım aşamasında Duru için gerekli yapıyı hazırlayalım, bunun için öncelikle ilgili callbackleri hazırlayalım. Ayrıca ilgili AT komutlarının kullandığınız modeme göre değişiklik gösterebileceğini unutmayınız.

/* İlgili sunucuya modem üzerinden ftp kanalı açılır */
uint8_t ftp_connect(void* self)
{	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
	
	char cr = {0x0D};
	
	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPOPEN=\"%s\",%i%c","ftpservername", 21, cr);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Sunucu üzerinde bulunan dosyalar listelenir, ilgili dosyanın olması durumunda yeni versiyon olup olmadığı flash üzerinde saklanılan adresten sorgulanır */
uint8_t parse_list_file (void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "CONNECT\r\n");
		
		if (ret != NULL)
		{
			ret = ret + sizeof("CONNECT\r\n");
			
			int i = 0;
			
			memset(gsm_uart.update_file_name, 0, sizeof(gsm_uart.update_file_name));
			
			while (*ret != '\r')
			{
					gsm_uart.update_file_name[i] = *ret++;
					i++;
			}

			ret = strstr(gsm_uart.update_file_name, "can_");
			
			if (ret != NULL)
			{
				ret = ret + sizeof("can");
				
				sscanf(ret, "%u.%u.%u", &gsm_uart.major, &gsm_uart.minor, &gsm_uart.bugfix);
				
				uint32_t current_version = *(uint32_t* )VERSION_ADDR;
				
				if (current_version == 0xFFFFFFFF)
				{
					is_any_new_firmware = true;
				}
				
				else
				{
					uint8_t major = current_version >> 24;
					uint8_t minor = current_version >> 16;
					uint8_t bug_fix = current_version >> 8;			
					
					char cv[10] = {0};
				
					snprintf(cv, sizeof(current_version), "%u.%u.%u", major, minor, bug_fix);
				
					int ret_version = comp_version(cv, ret);
					
					if (ret < 0)
					{
						is_any_new_firmware = true;
					}
					
					//Yeni update degil ise direkt olarak çik.
					else
					{
						struct command_machine* cm = (struct command_machine*)self;
						cm -> command_index = cm -> command_index + 3;
					}
				}
			}
		}	
	}
		
	return 0;
}
/* Dosya Modemin RAM bölgesine indirilir */
uint8_t ftp_get_file(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;

	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPGET=\"%s\",\"RAM:can.bin\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Dosyanın boyutu sunucudan istenilir */
uint8_t ftp_get_size(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
		
	char cmd[60] = { 0 };	
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPSIZE=\"%s\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
extern bool is_any_new_firmware;
extern float file_size;

/* Dosyanın boyutu ve dosya sorgulanır */
uint8_t ftp_file_size_cb(void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "OK\r\n\r\n");
		
		if (ret != NULL)
		{
			if (strstr(ret, "627") || strstr(ret, "550"))
			{
				is_any_new_firmware = false;
			}
			
			else 
			{
				ret = strstr(ret, "0,");
				
				if (ret)
				{
					ret = ret + 2;
					
					file_size = atof(ret);
					
					is_any_new_firmware = (file_size > 0) ? true : false;
				}
			}
		}
	}
	
	return 0;
}
/* Versiyonlar karşılaştırılır */
static int comp_version (const char* cv_version1, const char* version2 ) {
    unsigned major1 = 0, minor1 = 0, bugfix1 = 0;
	
    unsigned major2 = 0, minor2 = 0, bugfix2 = 0;
	
    sscanf(cv_version1, "%u.%u.%u", &major1, &minor1, &bugfix1);
	
    sscanf(version2, "%u.%u.%u", &major2, &minor2, &bugfix2);
	
    if (major1 < major2) return -1;
    if (major1 > major2) return 1;
    if (minor1 < minor2) return -1;
    if (minor1 > minor2) return 1;
    if (bugfix1 < bugfix2) return -1;
    if (bugfix1 > bugfix2) return 1;
    return 0;
}
/* Eğer herhangi bir güncelleme yok ise sorgulama durdulur */
int reset (void* self)
{
	if (self != NULL)
	{		
		struct command_machine* cm = (struct command_machine*)self;
		
		cm -> command_index = cm -> command_index + 3;
		
		//machine_stop_current(self);
	}
	
	return 0;	
}

Gelelim ilgili komutları ve komut pencerelerini hazırlamaya,

struct command ftp_open = {"FTP", "QFTPOPEN: 0,0\r\n", NULL, NULL, NULL, 2000, &ftp_connect };

struct command ftp_set_current_dir = {"AT+QFTPCWD=\"/\"\r\n", "QFTPCWD: 0,0", NULL, NULL, NULL, 3000, NULL };

struct command ftp_get_list_file_names = {"AT+QFTPNLST=\"/\",\"COM:\"\r\n", "+QFTPNLST:", NULL, NULL, &parse_list_file, 3000, NULL, reset};

struct command ftp_get_file_size = {"get_size", "QFTPSIZE:", NULL, NULL, &ftp_file_size_cb, 3000, &ftp_get_size };

struct command ftp_get_file_to_ram = {"get_file", "QFTPGET:", NULL, NULL, NULL, 3000, &ftp_get_file };

struct command ftp_close = {"AT+QFTPCLOSE\r\n", "+QFTPCLOSE: 0,0", NULL, NULL, NULL, 1500, NULL };

struct command ftp_deactivate_pdp = {"AT+QIDEACT=1\r\n", "OK\r\n", NULL, NULL, NULL, 4000, NULL };
const struct command* ftp_command_list_table[] = 
{
	&ftp_open,
	&ftp_set_current_dir,
	&ftp_get_list_file_names,
	&ftp_get_file_size,
	&ftp_get_file_to_ram,
	&ftp_close,
	&ftp_deactivate_pdp,	
};

Ve yeni bir güncelleme var ise artık dosyamızı sd karta yazdırabiliriz.

		if (is_any_new_firmware  && get_command_window_status(&command_machine_t, "FTP"))
		{
			// Herhangi bir yarım kalma durumları için dosya var olup olmaması sınanır.
			if ((fr = f_open(&file, "can.bin\0", FA_READ)) == FR_NO_FILE)
			{
					//Nothing.
			}
			
			else
			{
				f_close(&file);
				
				f_unlink("can.bin\0");
			}
			
			temp = file_size / 1000;
			
			temp_remainig = (file_size) - (temp * 1000);
		
			uart_tx_platform_specific((uint8_t *)"AT+QFOPEN=\"RAM:can.bin\",2\r\n", strlen("AT+QFOPEN=\"RAM:can.bin\",2\r\n"));
		
			HAL_Delay(500);
			
			while(temp-- > -1)
			{		
				int data_size = temp > -1 ? 1000 : temp_remainig;
			
				char temp_buf[75] = { 0 };
				
				snprintf(temp_buf, sizeof(temp_buf), "AT+QFREAD=3000,%d\r\n", 1000);
				
				clear_gsm_uart(&gsm_uart);	
				
				uart_tx_platform_specific((uint8_t *)temp_buf, strlen(temp_buf));
				
				HAL_Delay(1500);
			
				if (strlen(gsm_uart.buffer) > 0)
				{						
					char* ret = NULL;
					
					ret = strstr(gsm_uart.buffer, "CONNECT");
					
					if (ret != NULL)
					{
						ret = ret + sizeof("CONNECT");
						
						while (*ret++ != '\n');
						
						if ((fr = f_open(&file,(char *)"can.bin\0", FA_OPEN_APPEND | FA_WRITE)) == FR_OK)
						{												
							UINT bw;
							
							f_write(&file, ret, data_size, &bw);  
							
							fr = f_close(&file);	
						}						
					}
				}
			}
			
			NVIC_SystemReset();
		}

Aynı MCU içerisinde daha önce yazmış olduğum STM32 SD KART ÜZERİNDEN BOOT ETMEK içeriğinde bulunan yazılım her reset sonrasında çalıştırılmakta.

Eğer modeminiz destekliyorsa, ilgili dosyayı modemin RAM bölgesine indirilmesini sağlamak gerçekten güzel bir kolaylık sağlamakta. Buna ek olarak ilgili SSL sertifikalarını kullanarak rahatlıkla FTPS işlemini de yapabiliyorsunuz.

Sözün özü; Duru kütüphanesini kullanarak güncelleme yapmak bu kadar basit 🙂

Herkese iyi çalışmalar dilerim.

Raspberry Pi | Buildroot | QT Projesi


Buildroot Nedir ?

Buildroot, gömülü bir sistem için eksiksiz bir şekilde Linux sistemi oluşturma sürecini basitleştiren ve bu süreci otomatikleştiren açık kaynak bir araçtır. Hedef sistem için, çapraz derleme araçları, kök dosya sistemi, kernel imajı ve bootloader üretebilir. Tüm bunları hedef sistem için birbirinden bağımsız şekilde de yapmanıza olanak sağlar. Örneğin çapraz derleme yapılabilmesi için ilgili araçlara sahip olduğunuz bir hedef sisteme sadece kök dosya sistemi buildroot üzerinden ürettirilebilir. Farklı bir çok hedef sistem ve versiyonları için destek vermektedir.

Buildroot aracını windows işletim sistemi üzerinde çalıştırabilir miyim ?

Hayır, buildroot linux sistemler üzerinde koşabilecek şekilde tasarlanmıştır. Windows makineniz üzerine kuracağınız bir sanal makine üzerinden çalışmalarınızı yapabilirsiniz. Bu yazının hazırlanma sürecinde kullanılan linux dağıtımı ve versiyonu Ubuntu 18.04.5 LTS‘dir.

Buildroot aracını kullanmak için sistemde bulunması gerekli olan araçlar var mıdır ?

Evet, buildroot ilgili bir çok paketi kendisi oluşturacak olmasına rağmen bazı sistem gereksinimleri vardır. Host sistem içerisinde olması zorunlu paketler şunladır ;

  • which
  • sed
  • make (version 3.81 or any later)
  • binutils
  • build-essential (only for Debian based systems)
  • gcc (version 4.8 or any later)
  • g++ (version 4.8 or any later)
  • bash
  • patch
  • gzip
  • bzip2
  • perl (version 5.8.7 or any later)
  • tar
  • cpio
  • unzip
  • rsync
  • file (must be in /usr/bin/file)
  • bc

Yukarda bahsi geçen araçlara ek olarak opsiyonel olarak yükleyebileceğiniz bazı araçlarda vardır.

İlgili araçların kurulumları için apt programını kullanabiliriz. (apt vs apt-get için ilgili yazıyı okuyabilirsiniz)

sudo apt install sed make binutils gcc g++ bash patch \gzip bzip2 perl tar cpio python unzip rsync wget libncurses-dev

Eğer 64 bit platform üzerinde çalışıyor iseniz, şu komutu da çalıştırmanız gerekmektedir.

apt-get install gcc-multilib

Buildroot aracına nereden ulaşabilirim ?

https://buildroot.org/download.html sitesine giderek Long Term Support versiyonunu linux tabanlı sisteminize indirmeniz gerekmektedir. İndirme işleminden sonra tüm dosyaları çalışmak istediğiniz bir dizine çıkartabilirsiniz.

volkan@volkan:~/Downloads$ tar -xvf buildroot-2020.02.10.tar.gz -C ~/buildroot_working_dir

Yukarda bulunan komut ile Downloads klasörü altına indirmiş olduğum buildroot-2020.02.10.tar.gz dosyasını home dizini altında buildroot_working_dir klasörü içerisine çıkartıyorum. Tar programının almış olduğu komut satırı argümanları XVF sırasıyla, Extraxt, Verbose, File anlamlarına gelmektedir. Eğer C argümanı olmasaydı bulunduğu dizin içerisine çıkartacaktı. İlgili komut ile başka bir dizine dosyaları çıkarmasını sağladık. Bu argüman sonrasına aldığı dizin eğer custom bir dizin ise yukarda örnekte olduğu gibi, daha önce oluşturulmuş olması gerekiyor. Aksi takdirde belirtilen dizin için Cannot read : Is a directory hatasını alabilrsiniz.

Bu süreçten sonra ilgili dizine geçiş yaparak artık orada çalışmanız gerektiğini unutmayın !

Hedef sistem için konfigürasyon oluşturmak

Bu aşamada mevcut konfigürasyonları bir template gibi görebilir ve onların üzerinden devam edebileceğiniz gibi, sıfırdan kendi konfigürasyon dosyasınıza oluşturabilirsiniz. İlgili tüm konfigürasyonlar configs/ dizini altında bulunmaktadır. Kendi donanım sürecimde Raspberry Pi 3 modelini kullandığım ve QT gibi paketlerin seçilmiş olmasını tercih ettiğim için;

make raspberrypi3_qt5we_defconfig

komutunu giriyorum. Bu komutun başarıyla sonuçlandığını gösteren çıktı;

#
# configuration written to /home/volkan/buildroot_working_dir/buildroot-2020.02.10/.config
#

Şimdi oluşturacağımız dağıtıma ekleyeceğimiz paketlerin seçimini yapabileceğimiz bir pencereye geçiş yapabiliriz. Bu yapılan seçimler belirlediğimiz konfigürasyon üzerine yazılacaktır. Geçiş yapacağımız pencere curses tabanlı bir arayüz olup, kullanımı oldukça rahattır. Bunun için;

make menuconfig

Sizi karşılayan pencerenin görselini yukarda görebilirsiniz. Kendi(host) sistemimiz üzerinden hedef(target) sisteme yazılım üretebilmek çalışmasına kabaca çapraz-derleme(cross-compile) denmektedir. Bu çalışmayı yapabilmemiz için gerekli toolchainleri kendi makinemizde göstermemiz gerekiyor. Bu yüzden ilk aşama Toolchain sürecini yönetmek, 3.seçenek olan Toolchain’e geçiş yapalım.

Sizi karşılayacak pencere;

Çapraz Derleme Toolchain’i konusunda buildroot iki tane çözüm sunmakta ve bunlar sırasıyla. Buildroot Toolchain ve External Toolchain‘dir. Default olarak Buildroot Toolchain seçili durumdadır. İkisi arasından seçiminizi Toolchain type (Buildroot toolchain) menüsü üzerinden yapabilirsiniz. Bugün External Toolchain seçimini yaparak devam ediyoruz. Bunu seçmemizin sebebi, iyi bilinen ve test edilmiş bir toolchain seçimi yapmak. Eğer Buildroot Toolchain seçili kalsaydı, yapılacak konfigürasyonlara özgü buildroot gerekli toolchain’i kendisi derleyip oluşturacaktı. External Toolchain seçimi yapıldıktan sonra görebileceğiniz pencere;

Dilerseniz Toolchain kısmında Linaro Toolchain‘i seçebilirsiniz ben Arm ARM olarak devam ediyorum. Görsel de seçili değil ancak ek olarak, debug yapmanızın gerektirdiği durumlarda ihtiyaç duyacağımız Copy gdb server to the Target seçeneğini de ekleyebiliriz.

Ana menüye dönerek sistem bazlı ayarlarımızı yapmak için, System Configuration penceresine dallanabiliriz.

Sizi karşılayacak pencere;

Bu pencere içerisinde System hostname, System banner, Root password gibi ayarları yapabilirsiniz. Buna ek olarak /dev seçeneği altında buildroot 4 farkı opsiyon sunmaktadır. Cihazlar eklenip kaldırıldığında kullanıcı alanının bilgilendirmesi istenildiğinde Dynamic using devtmpfs + mdev seçeneğinin seçilmesini iyi bir çözüm olduğu belirtilmiş. Default durumda o şekilde gelmekte ve bende ayarlarını değiştirmedim. Buna ek olarak pencere alt tarafa gittikçe görebileceğiniz Install timezone info seçeneğini de aktif edebilirsiniz.

Şimdi Kernel penceresinde herhangi bir değişiklik yapmadığım için o kısmı geçiyorum. Dilerseniz inceleyebilirsiniz.

Artık sisteme ekleyeceğimiz uygulamaları seçebiliriz. Bunu ekleyebileceğimiz sekme Target Packages sekmesidir. Bu sekme içerisinde bir çok paket bulunmakta. Şayet paketin adını biliyorsanız “/” tuşu ile arama penceresine geçiş yapabilir ve paket adını yazarak hangi menü dallanmasından sonra ilgili paketi aktif edeceğinizi görebilirsiniz. Örneğin nano editörünü kurmak istiyoruz;

Arama yardımıyla, bu text editörün, Target Packages -> Text editor and viewers altında bulunduğunu görebildik. Bu şekilde ilgil dizine giderek ekleme yapabilirsiniz.

QT modülünün eklenmesinde bir kaç ince detay olduğu için, onları birlikte yapalım. Öncelikle Target Packages -> Graphic libraries and applications sekmesine giderek Qt5 seçeneğini bulalım. Eğer benimle aynı konfigürasyon dosyasını belirlediyseniz, Qt5 sisteminizde ekli olarak gözükecektir. Qt5 içerisine girerek uygulamarınızda kullanmayı düşündüğünüz kütüphaneleri seçmeniz gerekiyor. Bazıları seçili gelirken bazı paketler seçili gelmeyecektir. Örneğin, seri port işlemleri içeren qt ortamında bir yazılım geliştiriyorsanız, qt5serialport paketini seçmeniz gerekmektedir.

Not : Eğer benimle aynı konfigürasyonu seçmediyseniz ve arayüz tabanlı bir uygulama geliştirecekseniz gui module‘u seçmeniz gerekmektedir. Ayrıca Raspberry Pi videocore eglfs destekli çalıştığı için, Default Graphical Platform olarak eglfs‘i seçmeniz gerekmektedir.

Yukarda bahsettiğim Raspberry Pi’nin videocore sürücüsünü kullanabilmek için, (farklı bir konfigürasyon seçtiyseniz ya da kendi herhangi bir template konfigürasyon seçmediyseniz) Target Packages -> Hardware Handling -> rpi-userland seçeneğini aktif etmeniz gerekmektedir.

Burada tüm paketleri tek tek açıklamam mümkün değil, ihtiyaca göre şekilleniyor gördüğünüz gibi. İçerisinde binlerce paket bulunmakta, eğer dokunmatik ekran kullanıyorsanız Tslib seçeneğini aktif edebilirsiniz. Ya da kablosuz ağlara bağlanmak istiyorsanız, Target Packages -> Networking applications -> wpa_supplicant paketini de eklemeniz gerekmektedir.

Önemli olduğunu düşündüğüm ve bu yazının devamında yapacağımız proje için zorunlu olan bir ayara değineceğim. Bugün QT üzerinden gerçekleştireceğimiz bir projede, nöbetçi eczaneleri ekran üzerinde listelemeye çalışacağız. Kullanacağımız API, HTTPS üzerinden çalıştığı için QT5’e SSL paketini eklememiz gerekmektedir. Şayet bu şekilde bir derleme yapmazsanız, HTTPS kullandığınız istekleriniz başarısız olacaktır. Bunu eklemek için, Target Packages -> Graphic libraries and applications içerisine giderek QT5 penceresine girelim. Burada Custom configuration options kısmına -openssl yazmanız gerekmektedir.

Sistemimiz için gerekli konfigürasyonları yapmış durumdayız. Exit diyerek tüm pencerelerden çıktığımızda konfigürasyonlarımızı kaydetmek istediğimizi belirterek ayrılıyoruz.

Daha önce seçmiş olduğumuz konfigürasyon üzerine yeni konfigürasyonlarımız yazılacaktır. Bu konfigürasyon dosyanızı saklamanızda fayda var. Ekibe yeni katılan ya da birlikte çalışacağınız arkadaşlarınıza bu konfigürasyon dosyasını iletmeniz gerekebilir.

Şimdi ise derleme sürecini başlatabiliriz, bu süreç boyunca sağlıklı bir internet bağlantı hızına sahip olmanız gerekmektedir. Seçtiğiniz paketlere göre ve belirlediğiniz paralel derleme argümanınıza göre bu süreç elbette değişkenlik gösterebilir. Sanal makine kullanıyorsanız ayırdığınız disk boyutunun yeterli olması gerektiğini unutmayın. Eğer yeterli değilse, bir süre sonra aşağıda bulunan hataya benzer hatalar alabilirsiniz.

cc1: out of memory allocating 66574076 bytes after a total of 148316160 bytes
make

komutu ile derleme işlemini başlatabiliriz. Eğer paralel derleme yapmak istiyorsanız, örneğin;

make -j4

4 çekirdek üzerine bu süreci bölebilirsiniz. Burada önemli olan sisteminizin bu çekirdek sayısını destekliyor olup olmadığıdır. Şayet windows makineniz üzerinde sanal makine kurmuşsanız ve bu makineyi tek çekirdek olarak belirlediyseniz, yazdığınız 4’ün bir anlamı olmayacaktır. Aşağıda bulunan komutu kullarak, linux makineniz için Core(s) per socket değerini görebilirsiniz.

lscpu

Derleme süreci sonrasında, çalıştığınız dizin içerisinde /output/images altında sdcard.img ismiyle oluşacaktır. Bu dosyayı sd karta yazdırmak için ben balenaEtcher programını kullandım, dilerseniz bu adımı komut satırı üzerinden de gerçekleştirebilirsiniz. Tamamen size kalmış bir durum. Yazdırma işleminden sonra, kartınızı Raspberry Pi’ye takarak testinize gerçekleştirebilirsiniz.

Donanımınıza ssh üzerinden root olarak erişim sağlamanız gerekecektir. Bunun için, sshd_config dosyası içerisinde bulunan PermitRootLogin‘i YES olarak değiştirmeniz gerekmektedir.

Raspberry pi üzerinde bulunan debug uart /dev/ttyAMA0 portunu kendi uygulamarınızda kullanmak istiyorsanız yapmanız gereken bazı ayarlar var. Eğer böyle bir şeye ihtiyacınız yok ise bu adımı geçebilirsiniz.

Bunun için, öncelikle sistem dosyalarını mount etmemiz gerekmekte. Bunun için mount komutunu kullanacağız, –t komut satırı argümanı ile öncelikle dosya formatını giriyoruz. Daha sonrasında nereden nereye mount edeceğimiz bilgisini giriyoruz. mmcblk0 raspberry pi’nin kendi sistemini bulundurduğu yer ve bunu sd kart ile eşleştirmemiz gerekiyor.

mount -t vfat /dev/mmcblk0p1 /mnt

Ek not : board/raspberrypi3.cfp içerisinde hangi dosyaların görüntülenebilmesi için mount edilmesi gerektiği yazmakta.

image boot.vfat {
vfat {
files = {
“bcm2710-rpi-3-b.dtb”,
“bcm2710-rpi-3-b-plus.dtb”,
“bcm2710-rpi-cm3.dtb”,
“rpi-firmware/bootcode.bin”,
“rpi-firmware/cmdline.txt”,
“rpi-firmware/config.txt”,
“rpi-firmware/fixup.dat”,
“rpi-firmware/start.elf”,
“rpi-firmware/overlays”,
“zImage”
}
}
size = 32M
}
….

Şimdi ise, nano /mnt/config.txt dosyasına giderek, son satıra ekleme yapıyoruz.

enable_uart=1

Daha sonra inittab içerisinde getty’i kapatmanız gerekiyor. Bunun için nano /etc/inittab dosyasına giderek,

console::respawn:/sbin/getty/ -L console 0 vt100

ilgili satırı yorum satırı haline getirelim.

Tüm bu adımlardan sonra sisteminizi tekrar başlatmanız gerekir.

Host Tarafında Bulunan QT’de Target Platform İçin Kit Oluşturmak

Tüm bu süreçlerden sonra bizim için önemli olan, çapraz derleme yapmamızı sağlayacak ayarları artık host tarafında yapabiliriz. QT Creator programını açarak Tools -> Option -> Build & Run -> Qt Versions sekmesine gidiyoruz. Daha önce derlediğimiz buildroot klasörümüzün olduğu yere giderek /output/host/bin/qmake dosyasını ekliyoruz.

Daha sonra Compilers sekmesine giderek, ADD → GCC → C yaparak, output/host/bin/arm-none-linux-gnueabihf-gcc ekliyoruz, tabi Cpp için de gerekli toolchain dosyasını vermek gerek. Yine aynı şekilde ADD → GCC → C++ dedikten sonra output/host/bin/arm-none-linux-gnueabihf-g++ seçiyoruz.

Yukarda bulunan adımları yaptıktan sonra Kits sekmesinden yeni bir kit ekliyoruz, Kit ekleme bölümünde bulunan yapıda

  • Device Type olarak → Generic Linux Device
  • Sysroot tarafında, output/host/arm-buildroot-linux-gnueabihf/sysroot klasörünü gösteriyoruz.
  • Compiler sekmesi altında bulunan kısımda daha önce eklediğimiz C ve C++’ veriyoruz.
  • Qt version : host olacak şekilde seçim yapıyoruz.

Artık Devices sekmesi altında yazacağımız kodu raspberry pi üzerinde çalıştırmak için gerekli hostname, username, passowrd kısımlarını giriyoruz. Device olarak eklediğiniz kiti seçmeniz gerekir. Bu işlemlerden sonra isterseniz Test sekmesine basarak test yapabilirsiniz. Önemli bir not, sanal makine üzerinde çalışıyor ve ethernet kartını bridge olarak seçmemişseniz, raspberry pi ile aynı ağda olmayacağınız için bağlantı sağlanmayacaktır. O yüzden sanal makinenizin ayarları üzerinden köprülemeyi unutmayın.

Daha sonra açıklayacağım ama sd kart üzerinde yapmanız gereken bir işlem bulunmaktadır. Host tarafında geliştirdiğiniz projenizi, target platforma gönderdikten sonra uygulamayı çalıştırdığınızda free disk ile ilgili bir hata alacaksınız. Bu durumda sd kartın resize yapılması gerekiyor. Çünkü buildroot üzerinden oluşturduğunuz sistem tam fit olacak şekilde bir imaj oluşturmakta. Konfigürasyon dosyanızı incelerseniz şöyle bir ibare görülebilir.

BR2_TARGET_ROOTFS_EXT2_SIZE="400M"

Gparted programı ile resize işlemini yapabilirsiniz. Grafik arayüze sahip olduğu için kullanımı oldukça kolay. İlgili storage seçimini yaptıktan sonra (/dev/sdb2 , ext4 ile formatlı yer burası. Diğer taraf rootfs, oraya herhangi bir şey yapmayın.) kendiniz istediğiniz kadar yer ayırabilirsiniz.

QT’de Proje Oluşturma Süreçleri ve Nöbetçi Eczane Uygulaması

QT üzerinde her zaman yaptığınız gibi projenizi oluşturabilirsiniz, projeyi oluştururken dikkat etmeniz gereken kısım kit olarak daha önceden oluşturduğumuz kiti seçmelisiniz. Oluşturduğunuz proje için Project sekmesinden Shadow build seçeneğini kapatmanız gerekmektedir.

Daha sonra .pro uzantılı dosyanıza aşağıda bulunan ayarı eklemeniz gerekir. Bu ayar sayesinde, projenizi deploy ettiğiniz zaman target platformda root altına gidecektir.

target.path = /root
INSTALLS += target

Nöbetçi eczane listesini çekebilmek için https://collectapi.com/tr/api/health/pharmacy-api sitesinde bulunan API’yi kullanabiliriz. Siteye üye olduktan sonra, kullanmak istediğiniz API’ye gittiğinizde Free modele abone olabilirsiniz. Size 100 tane istek yapmanıza olanak sağlıyor. Profiliniz kısmından hesabınızla ilişkilendirilmiş token’ı görebilirsiniz.

API’nin kullanımı site üzerinden görebiliriz. QML içerisinde kod yazımı gerçekleştireceğimiz için javascript penceresi bizi ilgilendirmekte.

Main.qml

import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.11

Window {

    id: root
    visible: true
    maximumWidth: 720
    minimumWidth: 720
    maximumHeight: 1920
    minimumHeight: 1920

    Rectangle {

        width: parent.width
        height: parent.height
        border.width: 0
        color: "lightblue"

        ListModel {

            id: busModel
        }

        Component {

            id: busDelegate

            Item {

                id: delegateColumn
                width: root.width
                height: 190

                RowLayout {

                    id: row
                    anchors.fill: parent
                    spacing: 50

                    Text {

                        id: line
                        font.bold: true
                        text: name
                        font.family: "Helvetica"
                        font.pointSize: 18
                        Layout.fillWidth: true
                        Layout.minimumWidth: 100
                        Layout.preferredWidth: 45
                        Layout.maximumWidth: 120
                        wrapMode: Text.WordWrap
                    }

                    Text {

                        text: address
                        font.bold: true
                        font.family: "Helvetica"
                        font.pointSize: 20
                        Layout.fillWidth: true
                        Layout.minimumWidth: 300
                        Layout.preferredWidth: 300
                        Layout.maximumWidth: 300
                        Layout.alignment: Qt.AlignCenter
                        wrapMode: Text.WordWrap
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }

                    Text {

                        id: timertext
                        ColorAnimation on color {
                            id: anim
                            to: "red"
                            duration: 300
                        }

                        font.bold: true
                        text: phone
                        font.family: "Helvetica"
                        font.pointSize: 20
                        Layout.alignment: Qt.AlignCenter
                    }
                }

                Line {
                }
            }
        }

        ListView {

            anchors {
                fill: parent
            }
            model: busModel
            delegate: busDelegate
        }
    }

    function request(url, callback) {

        var xhr = new XMLHttpRequest()
        xhr.withCredentials = true

        xhr.onreadystatechange = (function (myxhr) {
            return function () {
                if (myxhr.readyState === 4) {
                    callback(myxhr)
                }
            }
        })(xhr)

        var data = null
        xhr.open("GET", url)
        xhr.setRequestHeader("Content-Type", "application/json")
        xhr.setRequestHeader(
                    "authorization",
                    "apikey yourtoken")
        xhr.send(data)
    }

    Timer {

        id: timer
        interval: 60000
        running: true
        repeat: true
        triggeredOnStart: true
        onTriggered: request(
                         "https://api.collectapi.com/health/dutyPharmacy?ilce=%C3%87ankaya&il=Ankara",
                         function (o) {
                             console.log(o.responseText)
                             if (o.status === 200) {

                                 busModel.clear()

                                 var obj = JSON.parse(o.responseText).result
                                 for (var i = 0; i < obj.length
                                      && i < 12; ++i) {
                                     busModel.append({
                                                         name: obj[i].name,
                                                         address: obj[i].address,
                                                         phone: obj[i].phone
                                                     })
                                 }
                             } else {

                                 console.log(" Connection Failed -> " + o.status)
                             }
                         })
    }
}

Line.qml

import QtQuick 2.0

Rectangle{
    width: parent.width
    height: 1
    border.width: 1
    border.color: "gray"
    color: "gray"
    anchors.horizontalCenter: parent.horizontalCenter
}

Hazırladığımız projeyi Build ettikten sonra hedef sisteme Deploy edebiliriz. Benim oluşturduğum proje adı untitled olarak isimlendirilmişti. Görselde görebileceğiniz gibi, root altına gelmiş durumda. ./untitled diyerek projeyi çalıştırabiliriz.

Raspberry Pi’nin hdmi çıkışı üzerinden kullandığım ekranı bu linkte bulabilirsiniz. Bu ekran ya da başka bir ekran olsun raspberry pi üzerine taktığınızda bir takım ayarlar yapmanız gerekiyor. Ekran çözünürlüğü, şayet ihtiyaç var ise ekran yönünün çevrilmesi gibi. Bu ayarları config.txt içerisinden ayarlayabiliriz. Daha önce bahsettiğim gibi sistem dosyalarını başka bir dizine mount etmemiz gerekiyor öncelikle.

Bu işlem sonrasında , config.txt içerisine girerek kendi ekranımız için şu 2 satırı giriyoruz.

display_rotate=3
hdmi_cvt=1920 720 60 3 0 0 0

Komutlardan anlayabileceğiniz, ekranı döndürme işlemi gerçekleştiriyor ve ekran uzunluk, genişlik, yenileme hızı gibi parametrelerini girmekteyiz. Uygulamayı çalıştırdığımda aldığım ekran görüntüsünü aşağıda görebilirsiniz.

Bu yazıyı hazırlama, bu bilgileri deneyimleme, öğrenme sürecinde başta Metin Koç olmak üzere aşağıda bulunan kaynakların sahiplerine de teşekkür ederim.

17.03.2021 Ekleme :

Ayarladığınız ekran çözünürlüğü enerji kesip verdiğinizde eski haline dönüyor fakat reboot ettiğinizde doğru çalışıyor ise, açılış için ufak bir süre vermeniz gerekebilir. Bunun için config.txt dosyası içerisine aşağıda bulunan satırı ekleyebilirsiniz.

boot_delay_ms=1000

Kaynaklar :

C++ Kursu Notlarım


C ve Sistem Programcıları Derneği üzerinden almış olduğumuz C++ kursu ile ilgili notlarımı notion üzerinde toplamaya başladım. Buradan erişim sağlayabilirsiniz.

  • Notlar içerisinde olabilecek hatalardan eğitmen değil ben sorumluyumdur. Hataları bildirirseniz çok sevinirim.
  • Kursun eğitmeni Necati Ergin’dir.
  • Her hafta minumum 2 not eklemek hedefim.
  • Notları incelemek dili öğrenmek için yeterli olmayacaktır. Her konuda olduğu gibi pratik şart.)

OOP_C[0] = “Polymorphism”



We can say that polymorphism means having many forms. I can hear you say, “C language is not an OOP language”. Yes you are right, but there is a very fine line in this answer;

Answer can be yes or no, depending on:

if you ask “is C an object oriented language?”, the answer is “no” because it do not have object oriented constructors, keywords, semantic etc…

if you intend “can I realize OOP in C?”, the answer is yes, because OOP is not only a requirement of a language, but also a way of “thinking”, an approach to programming, before to touch some language or another. However the implementation of OOP in C (or any other language not natively designed to be OOP) will be surely “forced” and much hard to manage then any other OOP language, so also some limitation shall be expected.

There are some techniques in this subject.

taken from geeksforgeeks

Today, we will try to implement the structure at run time with virtual functions.(Dynamic or late binding)

#include "stdio.h"
#include "stdbool.h"

struct which_series
{
    const struct vtable* vptrinc;
    const char* name;    
};

struct vtable
{
    void (*is_watched)(struct which_series* self);
};

static void is_watched(struct which_series* self)
{
    printf("\r\n Yes, %s watched ", self -> name);
}

void is_watched_for_client(struct which_series* self)
{
    const struct vtable* vptr = *(&self -> vptrinc);
    
    if (vptr -> is_watched != NULL)
    {
        vptr -> is_watched(self);
    }

    else
    {
        printf("\r\n No, %s not watched yet ", self -> name);
    }    
}

int main()
{
    struct vtable spycraft = { &is_watched };
    struct which_series spycraft_series  = { &spycraft, "Spycraft" };

    struct vtable lupin = { (void*)0 };
    struct which_series lupin_series = { &lupin, "Lupin" };

    is_watched_for_client(&spycraft_series);
    is_watched_for_client(&lupin_series);
}

How it is works ?

NOTE: The alignment of the Rectangle structure and the inherited attributes from the Shape
structure is guaranteed by the C Standard WG14/N1124. Section 6.7.2.1.13 of this Standard, says:
“… A pointer to a structure object, suitably converted, points to its initial member. There may be
unnamed padding within a structure object, but not at its beginning”.

Again, since “vptrinc” is the first member in the “which_series” structure it actually has the same address as the instance self.

printf("\r\n Addr in the structure < %x > ", self -> vptrinc);
printf("\r\n spycraft addr < %x > ", &spycraft);
//gives same address

Gömülü Yazılım Geliştirici Sohbetleri


Herkese Merhabalar, uzun bir süredir blog yazmamışım. Bu içerikte bir blog yazısı sayılmaz aslında. Sadece bir bildirim göndermek istiyorum, hayattayım 🙂

Ve Gömülü Yazılım Geliştiricileri ile sohbet serisine başladım. Nerede mi? Youtube kanalımda tabi ki 🙂