Line Chart Algoritması

Başlatan Mucit23, 29 Haziran 2015, 13:06:59

Mucit23

STM32F103 ve 3.2" TFT üzerinde Line Chart oluştumam gerekiyor. ADC verisini kayar şekilde grafikte çizdirmem lazım. Bunun için bilindik hızlı çalışan bir algoritma varmıdır? Belki noktalar arası çizgiler çizerek yapabilirim ama daha önce denemiştim oldukça yavaş oluyor. Hızlı çalışan ve hızlı tepki veren bilgidiğiniz bir algoritma veya kod örneği varmı? En hızlı şekilde nasıl yaparım?

Mucit23

#1
Yaptım. Kendi bir algoritma kurdum. İlk önce Sabit aralıklarla örnek almak için Timeri 50ms aralıklarla ADC yi tetikleyecek şekilde kurdum. Her 50ms sonrasında Timer ADC yi ölçüm alması için tetikliyor. ADC çevrimi bitirdiğinde DMA yı tetikliyor DMA da veriyi alıp değişkenime yüklüyor ardından DMA Transfer complete kesmesine gidiyor. Kesme içerisinde de verileri dizi içerisinde sola kaydırmasını yapıyorum. Ardından ekran güncelleniyor ve işlem bir sonraki ADC sonucuna kadar bitmiş oluyor.

Sonuç aşağıdaki gibi.

! No longer available

mesaj birleştirme:: 01 Temmuz 2015, 09:28:04

Sormak istediğim birşey daha var. Alt tarafa girid oluşturmak istiyorum. Bunu nasıl yaparım? İstiyorumki X ve Y eksen çizgileri ekleyeyim. Numaralandırma yapayım ve Alt tarafa girid şeklinde ızgara yerleştireyim. Nokta nokta olabileceği gibi kare karede olabilir. Bunu nasıl yapılır sizce?

justice_for_all

Alıntı YapKesme içerisinde de verileri dizi içerisinde sola kaydırmasını yapıyorum. Ardından ekran güncelleniyor ve işlem bir sonraki ADC sonucuna kadar bitmiş oluyor.

Bunu Nasıl yapıyosun hocam ? Kaydırma işlemini nasıl yapıyosun ekran kaydırmasını?
Deneyip de başaramayanları değil, yalnızca denemeye bile kalkışmayanları yargıla.   Gökhan Arslanbay

Maxim

baya düzgün iş çıkarmışsın

makdeniz

#4
Gerçekten güzel bir çalışma olmuş... Tebrikler

Bu arada bende bir arm geliştirme kartı ve ekran arayışı içindeyim örnek olması için Geliştirme kartınızın ve ekranın modeli nedir ? Hangi araç ile programlıyorsunuz ?
Karanlık olmadan aydınlık, Ölüm olmadan yaşam mantıksız.

Mucit23

#5
Daha hızlı yapılabilir kesinlikle. Benim aklıma yatmayan noktalar var hala.

Ayrıca kullandığım kartta LCD FSMC kullanılarak sürülmüyor. Grafik ilk kurulumu biraz yavaş bu yüzden.

Kodlar aslında basit.

ADC ayarlarını aşağıdaki gibi yapıyoruz ilk önce.
#include "stm32f10x.h"
#include "Adc.h"


unsigned int ADC_Value;

/*ADC baslanagic ayarlari yuklenme islemini yapar */
void ADC1_Configuration(){
	
		ADC_InitTypeDef ADC_InitStructure;
		GPIO_InitTypeDef GPIO_InitStructure;

		RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);

		/* ADC pin analog input yapiliyor */
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;  
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
		GPIO_Init(GPIOC, &GPIO_InitStructure);

		ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
		ADC_InitStructure.ADC_ScanConvMode = DISABLE;
		ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
		ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
		ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
		ADC_InitStructure.ADC_NbrOfChannel = 1;
		ADC_Init(ADC1, &ADC_InitStructure);

		/* ADC1 Kanal ve clk secimi yapiliyor ( kanal_15 )  */ 
		ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 1, ADC_SampleTime_55Cycles5);

		ADC_Cmd(ADC1, ENABLE);
		
		ADC_DMACmd(ADC1,ENABLE);
		
		ADC_ExternalTrigConvCmd(ADC1,ENABLE);

		/* ADC1 Reset Kalibrasyonu yapilacak */
		ADC_ResetCalibration(ADC1);
		/* Kalibrasyon bekleniyor */
		while(ADC_GetResetCalibrationStatus(ADC1));

		/* ADC1 calibrasyonu yapilacak */
		ADC_StartCalibration(ADC1);
		/* Kalibrasyon bekleniyor */
		while(ADC_GetCalibrationStatus(ADC1));
		
		/*  ADC yi Baslatiyoruz  */
		//ADC_SoftwareStartConvCmd(ADC1,ENABLE); 
}

void DMA1_Configuration(void)
{
		DMA_InitTypeDef DMA_InitStructure;
		NVIC_InitTypeDef NVIC_InitStructure;
		/*DMA Clock Aktif Ediliyor*/
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

		/*  DMA Ayarlar1 Yapiliyor */
		DMA_DeInit(DMA1_Channel1);
		DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
		DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_Value;
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
		DMA_InitStructure.DMA_BufferSize = 1;
		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
		DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
		DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
		DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
		DMA_InitStructure.DMA_Priority = DMA_Priority_High;
		DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
		DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
		 /* Enable the TIM1 Interrupt */
		NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_Init(&NVIC_InitStructure);
		
		/* DMA Transfer Complete interrupt Enable*/
		DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE);	

		/* Enable DMA1 channel1 */
		DMA_Cmd(DMA1_Channel1, ENABLE);
}

void TIM3_Configuration(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	  TIM_OCInitTypeDef	TIM_OCInitStructure;
	
    /*Enable TIM2 Clock*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);		
    TIM_DeInit(TIM3);
    
    TIM_TimeBaseStructure.TIM_Period = 60000;	
    TIM_TimeBaseStructure.TIM_Prescaler = 60;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	
	  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
	
    TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_OC1); 
	
	  TIM_ARRPreloadConfig(TIM3, ENABLE);	
	
    TIM_Cmd(TIM3, ENABLE);
}

void ADC_init(void)
{
  TIM3_Configuration();
  DMA1_Configuration();
	ADC1_Configuration();
}


Burada önemli olan Timer ile düzgün aralıklarla örnek almak. Ben 20Hz ile örnek alıyorum. Örnek alma hızı nekadar artarsa grafik okadar hızlı bir şekilde kayar.

Sonra DMA interrupt Rutininde kaydırma yapıyoruz.

void DMA1_Channel1_IRQHandler(void)
{
	unsigned int i=0;
	
	GPIOD->ODR^=GPIO_Pin_8;     //Led toggle yapiliyor.
	
	for(i=0;i<315;i++)      //Veriler Sola kaydiriliyor.
	{
	   ADC_ConvertedValue[i]=ADC_ConvertedValue[i+1];
	}
	
	ADC_ConvertedValue[315]=(ADC_Value*200)/4095;   //Gelen adc degeri ekran yüksekligine göre oranlaniyor. 
	                                                //Max Grafik yüksekligi 200pixel yaptim.
	status=1;                                       //Ekran güncellenmesi için bayragi 1 yapiyoruz. 
	DMA_ClearITPendingBit(DMA1_IT_TC1);
}


Burada Grafiğin pixel genişliği kadar bir ram alanı oluşturmak gerekiyor. Benim grafiğimin genişliği 315 pixel olduğu için dizimin boyutunu 315 yaptım.
ADC den yeni gelen değer dizinin en son elemanına grafiğin yüksekliğine oranlayarak yerleştiriyoruz. Tabi bundan öncede dizi elemanlarını bir sola kaydırmak gerekiyor.

Sonra main programındada eğer status değişkeni 1 ise aşağıdaki fonksiyon ile grafiğimizi güncelliyoruz.

void Draw_Chart (void)
{
  unsigned int Xpos=0;

  for(Xpos=0;Xpos<315;Xpos++)
	{
		LCD_SetTextColor(White);  //Çizgi Rengini arka plan rengi yaptik. 
		LCD_Line(Xpos+2,237-Old_ADC_ConvertedValue[Xpos],Xpos+3,237-Old_ADC_ConvertedValue[Xpos+1]);//Eski Degerler siliniyor
		LCD_SetTextColor(Red);    //Çizgi Rengini Kirmizi Yaptik. 
		LCD_Line(Xpos+2,237-ADC_ConvertedValue[Xpos],Xpos+3,237-ADC_ConvertedValue[Xpos+1]); //Yeni Degerler çiziliyor.
	}
	
	for(Xpos=0;Xpos<316;Xpos++)
	{
	  Old_ADC_ConvertedValue[Xpos]=ADC_ConvertedValue[Xpos];//Eski degerler kaydediliyor. 
	}

}


Yapılan işlemler bundan ibaret.

Arkaya birde girid yapsam güzel olacak. Bunu düşünmem lazım.

justice_for_all

#6
void DMA1_Channel1_IRQHandler(void)
{
    unsigned int i=0;
    
    GPIOD->ODR^=GPIO_Pin_8;     //Led toggle yapiliyor.
    
    for(i=0;i<315;i++)      //Veriler Sola kaydiriliyor.
    {
       ADC_ConvertedValue[i]=ADC_ConvertedValue[i+1];
    }
    
    ADC_ConvertedValue[315]=(ADC_Value*200)/4095;   //Gelen adc degeri ekran yüksekligine göre oranlaniyor. 
                                                    //Max Grafik yüksekligi 200pixel yaptim.
    status=1;                                       //Ekran güncellenmesi için bayragi 1 yapiyoruz. 
    DMA_ClearITPendingBit(DMA1_IT_TC1);
}


Burda kafam takılan bişey var dma buffer 1 byte yapmışşsın.DMA sürekli ADC_ConvertedValue[0] elemanına yazmazmı adc değerini.

mesaj birleştirme:: 01 Temmuz 2015, 12:56:25

tamam ben DMA ya bu dizinin adresini verdin sandım sen byte olarak vermissin.

mesaj birleştirme:: 01 Temmuz 2015, 13:07:58

ADC_ConvertedValue 315 elemanlı bir diziyse son elemanı ADC_ConvertedValue[314] değilmi hocam.

ADC_ConvertedValue[315]=(ADC_Value*200)/4095;   //Gelen adc degeri ekran yüksekligine göre oranlaniyor.

ama burda neden boyle olmuş.
Deneyip de başaramayanları değil, yalnızca denemeye bile kalkışmayanları yargıla.   Gökhan Arslanbay

Mucit23

Pardon dizi boyutunu 315 tanımlayıp 315. Elemana veri alamadım keil hata verdi. Diziyi tanimlarken boyutu 316 yaptım.

CLR

50ms'de bir adc değeri okuyup dma kullanman mantıklı olmamış. 50ms içinde 316 adc okuyacak olsaydın mantıklı olacaktı. Ama sorun bu değil.

Problem aşağıdaki kod parçaçığı, gereğinden çok fazla iş yaptırıyorsun. Senin işini çözecek algoritma circular buffer veya ring buffer, bunları araştır. 

Hiç shift yapmana gerek yok.

Bunları dma veya dma'sız yapabilirsin. 50ms'de bir okuma varsa dma gereksiz olur.

for(i=0;i<315;i++)      //Veriler Sola kaydiriliyor.
    {
       ADC_ConvertedValue[i]=ADC_ConvertedValue[i+1];
    }
Knowledge and Experience are Power

serdararikan

@CLR haklısın bu tarz bir shift işlemi işlemciye gereksiz iş yükü.

Mucit23

#10
Alıntı yapılan: CLR - 01 Temmuz 2015, 15:19:57
50ms'de bir adc değeri okuyup dma kullanman mantıklı olmamış. 50ms içinde 316 adc okuyacak olsaydın mantıklı olacaktı. Ama sorun bu değil.

Problem aşağıdaki kod parçaçığı, gereğinden çok fazla iş yaptırıyorsun. Senin işini çözecek algoritma circular buffer veya ring buffer, bunları araştır. 

Hiç shift yapmana gerek yok.

Bunları dma veya dma'sız yapabilirsin. 50ms'de bir okuma varsa dma gereksiz olur.

for(i=0;i<315;i++)      //Veriler Sola kaydiriliyor.
    {
       ADC_ConvertedValue[i]=ADC_ConvertedValue[i+1];
    }


Nasıl yapacağım. Daha doğrusu kaydırma işlemi olmadan grafiğin sola kaydırmasını nasıl yapacağım? 50 ms aralıklarla bir örnek almak gerekmezmi 50ms içerisinde 316 örnek alıp bunları ekrana çizsem osiloskop benzeri birşey yapmış olmazmıyım? Bana bu lazım değilki.

Birde DMA nın circular buffer özelliğini nasıl kullanacam. Aslında asıl olması gereken 316 elemanlı bir FIFO. DMA doğrudan diziye FIFO mantığıyla veri yüklese hiç kaydırma işleriyle uğraşmam.

Birde DMA gereksiz demişsiniz. Bu neden? Gerçi normalde ADC kesmesini aktif edip kesme içerisindede adc değerini okuyup diziye gönderebilirim ama daha fazla işlem yapmış olacam.

Eksiklerimi atladığım noktaları anlatırsan sevinirim.

Edit; Yanlışım yoksa F4 de DMA'nın FIFO özelliği vardı ama F1 de yok galiba

muuzoo

Alıntı yapılan: CLR - 01 Temmuz 2015, 15:19:57
50ms'de bir adc değeri okuyup dma kullanman mantıklı olmamış. 50ms içinde 316 adc okuyacak olsaydın mantıklı olacaktı. Ama sorun bu değil.

Problem aşağıdaki kod parçaçığı, gereğinden çok fazla iş yaptırıyorsun. Senin işini çözecek algoritma circular buffer veya ring buffer, bunları araştır. 

Hiç shift yapmana gerek yok.

Bunları dma veya dma'sız yapabilirsin. 50ms'de bir okuma varsa dma gereksiz olur.

for(i=0;i<315;i++)      //Veriler Sola kaydiriliyor.
    {
       ADC_ConvertedValue[i]=ADC_ConvertedValue[i+1];
    }


Benzer iş yapan bir kod parçacığım var. 42 elemanlı bir diziyi her seferinde sola kaydırıyorum. Ring Buffer kullanmak burda kayda değer bir performans artışı sağlar mı acaba?
gunluk.muuzoo.gen.tr - Kişisel karalamalarım...

serdararikan

@Mucit23 bir adet index registerin olacak.bu register kayıtın dizinin hangi bölgesine yapılacağını tutacak.ekrana basma işini de indexten itibaren indexin bir önceki değerine kadar ekrana en baş sutundan itibaren basarak yapacaksın. böylece hiç kaydırma yapmayacaksın. ekrana basma işleminden sonra index değerini 1 artıracaksın

CLR

Sayısal değerleri örnek olsun diye veriyorum,

Aşağıdaki gibi bufferınız olsun, mesela her 10ms'de bir buffer'a veri atıyorsunuz diyelim ve buffer boyutu 100 olsun, buffer'ın dolması 1sn sürer.

Bu 100 veriyi ekrana her 10ms shift yaparak yazdırmak istiyorsunuz diyelim bunun için buffer içindeki "ekrana basma adresi" her defasında 1 artırılırsa hiç buffer registerleri shift yapmadan ekrandaki veri kaymış olur. Tabii ekrana basma adresi'de ve okunan adc datası adreside ring yapacak.   




Knowledge and Experience are Power

Mucit23

Anladım. DMA ile doğrudan diziye yazma yapabiliriz sizin mantıkla. Denemem lazım.