PB -5- Arduino DHT11 sensörü
Arduino’dan gelen sıcaklık ve nem bilgisini grafiksel (chart) olarak gösteriyoruz. Merak etmeyin, aşağıdaki 3 tane .pb uzantılı dosyanın toplam satır sayısı, demo PureBasic editörünün limiti olan 800 satırı geçmiyor.
Aşağıdaki kodu Arduino’ya yüklüyoruz.. Öncesinde, kütüphane yöneticisi ile DHT-sensor-library ve Adafruit-Unified-Sensor kütüphanelerini yüklüyoruz.
#include <DHT.h>
#define DHTPIN 2 //DHT pin
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(19200);
dht.begin();
}
void loop() {
float h = dht.readHumidity(); // Nem
float t = dht.readTemperature(); // Sıcaklık
Serial.print("TP"); // header
Serial.print(t);
Serial.print("@"); // separator
Serial.println(h); // println satır sonuna CRLF basar
delay(250);
}
Bu kodu Arduino’ya yükledikten sonra, her 250 milisaniyede bir sıcaklık ve nem bilgisi TP24.30@55 biçiminde Purebasic’e iletilecektir. Sensör sıcaklığı küsuratlı veriyor ama nemi tamsayı olarak veriyor. Forumda dolanırken resimde olduğu gibi sinüs, cosinüs veya başka matematiksel grafikleri zamana bağlı olarak çizebilen bir kod parçası buldum. graph100 isimli forum kullanıcısı yazmış, sanırım fransız kendisi.
Kodu biraz oynayarak, aşağıdaki hale getirdim. Kodu chart.pb olarak boş bir dizine kaydedin.
; https://www.purebasic.fr/english/viewtopic.php?t=60077&start=18
; chart module from user : graph100 in forum
DeclareModule Graph
Structure Graph_pt_point_d
x.d
y.d
EndStructure
Structure Graph_pt
*graph.Graph
color.l
line.b
List pt.d()
EndStructure
Structure Graph
pos.Graph_pt_point_d
dimension.POINT
nb_point.l
List list_des_pt.Graph_pt()
maxi.d
mini.d
ratio_w.d
ratio_h.d
Name.s
EndStructure
Declare.i Graph_Init(x, y, width, height, nb_point, Name.s = "")
Declare.i Graph_AddSerie(*graph.Graph, color.l, Line.b = #False)
Declare.i Graph_FreeSerie(*graph.Graph, *adresse_serie)
Declare Graph_AddPoint(*adresse_serie.Graph_pt, value.d)
Declare Graph_Draw(*graph.Graph)
EndDeclareModule
Module Graph
Procedure.d Maxi_delta(value.d, delta.d)
If delta = 0
ProcedureReturn Round(value, #PB_Round_Up) + 0.5
Else
p10.d = Pow(10, Round(Log10(Abs(delta)), #PB_Round_Down))
ProcedureReturn Round(value / p10, #PB_Round_Up) * p10
EndIf
EndProcedure
Procedure.d Mini_delta(value.d, delta.d)
If delta = 0
ProcedureReturn Round(value, #PB_Round_Down) - 0.5
Else
p10.d = Pow(10, Round(Log10(Abs(delta)), #PB_Round_Down))
ProcedureReturn Round(value / p10, #PB_Round_Down) * p10
EndIf
EndProcedure
Procedure.i Graph_Init(x, y, width, height, nb_point, Name.s = "")
*graph.Graph = AllocateMemory(SizeOf(Graph))
InitializeStructure(*graph, Graph)
*graph\pos\x = x
*graph\pos\y = y
*graph\dimension\x = width
*graph\dimension\y = height
*graph\nb_point = nb_point
*graph\ratio_w = *graph\dimension\x / *graph\nb_point
*graph\Name = Name
ProcedureReturn *graph
EndProcedure
Procedure.i Graph_AddSerie(*graph.Graph, color.l, Line.b = #False) ; serinin adresini döndürür
*new_serie = AddElement(*graph\list_des_pt())
InitializeStructure(*new_serie, Graph_pt)
*graph\list_des_pt()\color = color
*graph\list_des_pt()\graph = *graph
*graph\list_des_pt()\line = Line
ProcedureReturn *new_serie
EndProcedure
Procedure.i Graph_FreeSerie(*graph.Graph, *adresse_serie)
ChangeCurrentElement(*graph\list_des_pt(), *adresse_serie)
FreeList(*graph\list_des_pt()\pt())
DeleteElement(*graph\list_des_pt())
EndProcedure
Procedure Graph_AddPoint(*adresse_serie.Graph_pt, value.d)
LastElement(*adresse_serie\pt())
AddElement(*adresse_serie\pt())
*adresse_serie\pt() = value
If ListSize(*adresse_serie\pt()) > *adresse_serie\graph\nb_point
FirstElement(*adresse_serie\pt())
DeleteElement(*adresse_serie\pt())
EndIf
EndProcedure
Procedure Graph_Draw(*graph.Graph)
Line(*graph\pos\x - 1, *graph\pos\y - 1, *graph\dimension\x + 2, 1, #Green)
Line(*graph\pos\x - 1, *graph\pos\y - 1, 1, *graph\dimension\y + 2, #Green)
Line(*graph\pos\x - 1, *graph\pos\y - 1 + *graph\dimension\y + 2, *graph\dimension\x + 2, 1, #Green)
Line(*graph\pos\x - 1 + *graph\dimension\x + 2, *graph\pos\y - 1, 1, *graph\dimension\y + 2, #Green)
j = 4
For i = 69 To 621 Step 69
Line(*graph\pos\x + i, *graph\pos\y + *graph\dimension\y - 3, 1, 10, #Green)
DrawText(*graph\pos\x + i - 6, *graph\pos\y + *graph\dimension\y + 8, Str(j), #Green, 0)
j + 4
Next
DrawText(*graph\pos\x + 640, *graph\pos\y + *graph\dimension\y + 8, "secs", #Green, 0)
If *graph\Name
Protected titre$ = " " + *graph\Name + " "
Protected w = TextWidth(titre$), h = TextHeight(titre$)
Protected x.d = *graph\pos\x + *graph\dimension\x / 2 - w / 2, y.d = *graph\pos\y - h / 2
DrawText(x ,y , titre$, #Green)
Line(x - 1, y - 1, w + 1, 1, #Green)
Line(x - 1, y - 1, 1, h + 1, #Green)
Line(x - 1, y - 1 + h + 1, w + 2, 1, #Green)
Line(x - 1 + w + 1, y - 1, 1, h + 2, #Green)
EndIf
DrawText(*graph\pos\x + *graph\dimension\x + 2, *graph\pos\y - 1, StrD(*graph\maxi), #Green)
DrawText(*graph\pos\x + *graph\dimension\x + 2, *graph\pos\y - 1 + *graph\dimension\y +
2 - TextHeight(" "), StrD(*graph\mini), #Green)
*graph\ratio_h = *graph\dimension\y / (*graph\maxi - *graph\mini)
mini.d = *graph\mini
If Sign(*graph\maxi) <> Sign(*graph\mini)
Line(*graph\pos\x, *graph\pos\y + *graph\dimension\y + mini * *graph\ratio_h, *graph\dimension\x, 1, #Green)
DrawText(*graph\pos\x + *graph\dimension\x + 2, *graph\pos\y + *graph\dimension\y +
mini * *graph\ratio_h - TextHeight(" ") / 2, "0", #Green)
EndIf
*graph\maxi = -Infinity()
*graph\mini = Infinity()
ForEach *graph\list_des_pt()
index = 0
If *graph\list_des_pt()\line = #False
ForEach *graph\list_des_pt()\pt()
If *graph\list_des_pt()\pt() > *graph\maxi : *graph\maxi = *graph\list_des_pt()\pt() : EndIf
If *graph\list_des_pt()\pt() < *graph\mini : *graph\mini = *graph\list_des_pt()\pt() : EndIf
Line(*graph\pos\x + index * *graph\ratio_w, *graph\pos\y +
*graph\dimension\y - (*graph\list_des_pt()\pt() - mini)**graph\ratio_h,
1, 1, *graph\list_des_pt()\color)
index + 1
Next
Else
*adr.Double = FirstElement(*graph\list_des_pt()\pt())
old_x.d = *graph\pos\x + index * *graph\ratio_w
old_y.d = *graph\pos\y + *graph\dimension\y - (*graph\list_des_pt()\pt() - mini) * *graph\ratio_h
ForEach *graph\list_des_pt()\pt()
If *graph\list_des_pt()\pt() > *graph\maxi : *graph\maxi = *graph\list_des_pt()\pt() : EndIf
If *graph\list_des_pt()\pt() < *graph\mini : *graph\mini = *graph\list_des_pt()\pt() : EndIf
x.d = *graph\pos\x + index * *graph\ratio_w
y.d = *graph\pos\y + *graph\dimension\y - (*graph\list_des_pt()\pt() - mini) * *graph\ratio_h
LineXY(x, y, old_x, old_y, *graph\list_des_pt()\color)
old_x = x
old_y = y
index + 1
Next
EndIf
If LastElement(*graph\list_des_pt()\pt())
DrawText(*graph\pos\x + *graph\dimension\x + 2,
*graph\pos\y + *graph\dimension\y-(*graph\list_des_pt()\pt() - mini) * *graph\ratio_h - TextHeight(" ") / 2,
StrD(*graph\list_des_pt()\pt()), *graph\list_des_pt()\color)
EndIf
Next
delta.d = *graph\maxi - *graph\mini
*graph\maxi = Maxi_delta(*graph\maxi, delta)
*graph\mini = Mini_delta(*graph\mini, delta)
If delta = 0 : delta = 1 : EndIf
EndProcedure
EndModule
Geçen yazıda olduğu gibi burada da Mk-soft’un seri port’u thread ile okuyan kodunu kullandım. Aşağıdaki kodu da comPort.pb olarak aynı dizine kaydedin.
;-TOP
; https://www.purebasic.fr/english/viewtopic.php?t=79759
; Comment : Comport Manager Over Thread and Callback
; Author : mk-soft
; Version : v0.07.1
; Created : 26.01.2018
; Updated : 03.09.2022
; *****************************************************************************
CompilerIf #PB_Compiler_Thread = 0
CompilerError "Use Option Threadsafe!"
CompilerEndIf
Prototype ProtoReceiveCB(Text.s)
Prototype ProtoStatusCB(Status, *ComData)
Enumeration
#ComThread_Stopped
#ComThread_Startup
#ComThread_Running
EndEnumeration
Enumeration
#ComStatus_Nothing
#ComStatus_OpenPort
#ComStatus_ClosePort
#ComStatus_ErrorOpenPort
#ComStatus_ErrorSend
#ComStatus_ErrorReceive
#ComStatus_ErrorDataSize
EndEnumeration
Structure udtComData
; Header
ThreadID.i
Exit.i
Status.i
; Port Data
ComID.i
Port.s
Baud.i
Parity.i
DataBit.i
StopBit.i
Handshake.i
BufferSize.i
; End Of Text
EndOfText.s
; Send Data
SendSignal.i
SendCount.i
SendText.s
SendError.i
; Receive data
ReceiveCount.i
ReceiveText.s
ReceiveError.i
; Callback
*StatusCB.ProtoStatusCB
*ReceiveCB.ProtoReceiveCB
EndStructure
Procedure thComport(*ComData.udtComData)
Protected *Send, *Receive, SendText.s, SendLen, ReceiveText.s, ReceiveLen, Pos
With *ComData
; Startup
\Status = #ComThread_Startup
\SendCount = 0
\ReceiveCount = 0
\ComID = OpenSerialPort(#PB_Any, \Port, \Baud, \Parity, \DataBit, \StopBit, \Handshake, \BufferSize, \BufferSize)
If \ComID
\Status = #ComThread_Running
Else
If \StatusCB
\StatusCB(#ComStatus_ErrorOpenPort, *ComData)
EndIf
\Status = #ComThread_Stopped
ProcedureReturn 0
EndIf
If \StatusCB
\StatusCB(#ComStatus_OpenPort, *ComData)
EndIf
*Send = AllocateMemory(\BufferSize)
*Receive = AllocateMemory(\BufferSize)
; Loop
Repeat
If \SendSignal
SendText = \SendText + \EndOfText
SendLen = StringByteLength(SendText, #PB_Ascii)
If SendLen <= \BufferSize
PokeS(*Send, SendText, SendLen, #PB_Ascii)
If WriteSerialPortData(\ComID, *Send, SendLen) = 0
\SendError = SerialPortError(\ComID)
If \StatusCB
\StatusCB(#ComStatus_ErrorSend, *ComData)
EndIf
Else
\SendError = 0
\SendCount + 1
EndIf
Else
If \StatusCB
\StatusCB(#ComStatus_ErrorDataSize, *ComData)
EndIf
EndIf
\SendSignal = #False
EndIf
ReceiveLen = AvailableSerialPortInput(\ComID)
If ReceiveLen
ReceiveLen = ReadSerialPortData(\ComID, *Receive, ReceiveLen)
If ReceiveLen = 0
\ReceiveError = SerialPortError(\ComID)
If \StatusCB
\StatusCB(#ComStatus_ErrorReceive, *ComData)
EndIf
Else
\ReceiveError = 0
EndIf
ReceiveText + PeekS(*Receive, ReceiveLen, #PB_Ascii)
Repeat
pos = FindString(ReceiveText, \EndOfText, 1, #PB_String_NoCase)
If pos
\ReceiveText = Left(ReceiveText, pos - 1)
ReceiveText = Mid(ReceiveText, pos + Len(\EndOfText))
\ReceiveCount + 1
If \ReceiveCB
\ReceiveCB(\ReceiveText)
EndIf
EndIf
Until pos = 0
EndIf
Delay(10)
Until \Exit
; Shutdown
If IsSerialPort(\ComID) : CloseSerialPort(\ComID) : EndIf
If \StatusCB
\StatusCB(#ComStatus_ClosePort, *ComData)
EndIf
FreeMemory(*Send)
FreeMemory(*Receive)
\Status = #ComThread_Stopped
\ComID = 0
\Exit = 0
ProcedureReturn 1
EndWith
EndProcedure
; ----
Procedure.s SerialPortErrorText(ErrorCode)
Protected r1.s
Select ErrorCode
Case #PB_SerialPort_RxOver : r1 = "An input buffer overflow has occurred."
Case #PB_SerialPort_OverRun : r1 = "A character-buffer overrun has occurred."
Case #PB_SerialPort_RxParity : r1 = "The hardware detected a parity error."
Case #PB_SerialPort_Frame : r1 = "The hardware detected a framing error."
Case #PB_SerialPort_Break : r1 = "The hardware detected a break condition."
Case #PB_SerialPort_TxFull : r1 = "The application tried to transmit a character but the output buffer was full."
Case #PB_SerialPort_IOE : r1 = "An I/O error occurred during communications with the device."
Case #PB_SerialPort_WaitingCTS : r1 = "Specifies whether transmission is waiting for the CTS (clear-To-send) signal to be sent."
Case #PB_SerialPort_WaitingDSR : r1 = "Specifies whether transmission is waiting for the DSR (Data-set-ready) signal to be sent."
Case #PB_SerialPort_WaitingRLSD : r1 = "Specifies whether transmission is waiting for the RLSD (receive-line-signal-detect) signal to be sent."
Case #PB_SerialPort_XoffReceived: r1 = "Specifies whether transmission is waiting because the XOFF character was received."
Case #PB_SerialPort_XoffSent : r1 = "Specifies whether transmission is waiting because the XOFF character was transmitted."
Case #PB_SerialPort_EOFSent : r1 = "Specifies whether the end-of-file (EOF) character has been received."
Default : r1 = "ErrorCode " + Hex(ErrorCode)
EndSelect
ProcedureReturn r1
EndProcedure
; ----
; Threaded String Helper
Procedure AllocateString(String.s)
Protected *mem
*mem = AllocateMemory(StringByteLength(String) + SizeOf(Character))
If *mem
PokeS(*mem, String)
EndIf
ProcedureReturn *mem
EndProcedure
Procedure.s FreeString(*Mem)
Protected result.s
If *Mem
result = PeekS(*Mem)
FreeMemory(*Mem)
EndIf
ProcedureReturn result
EndProcedure
XIncludeFile komutu ile üstteki dosyaları kullanan aşağıdaki kodu da dht11.pb olarak aynı dizine kaydedin.
XIncludeFile "chart.pb"
XIncludeFile "comPort.pb"
Global ComData.udtComData, temperature$, humidity$
Define.f maxTemperature, maxHumidity
Enumeration EventCustomValue #PB_Event_FirstCustomValue
#My_Event_NewData
#My_Event_NewState
EndEnumeration
; ---------------------------------------------------------------------------
Procedure ReceiveCB(Text.s)
PostEvent(#My_Event_NewData, 0, 0, 0, AllocateString(Text))
EndProcedure
Procedure MyEventNewDataCB()
Protected Text.s
Text = FreeString(EventData())
If Left(Text, 2) = "TP"
temperature$ = StringField(Text, 1, "@")
temperature$ = Right(temperature$, (Len(temperature$) - 2))
humidity$ = Right(Text, Len(text) - (Len(temperature$) + 3))
EndIf
EndProcedure
BindEvent(#My_Event_NewData, @MyEventNewDataCB())
; ---------------------------------------------------------------------------
Procedure StatusCB(Status, *ComData.udtComData)
PostEvent(#My_Event_NewState, 0, 0, Status, *ComData)
EndProcedure
Procedure MyEventNewStateCB()
Protected Text.s, Status, *ComData.udtComData
Status = EventType()
*ComData = EventData()
Select Status
Case #ComStatus_OpenPort
Text = "ComStatus: Open Port " + *ComData\Port
Case #ComStatus_ClosePort
Text = "ComStatus: Close Port " + *ComData\Port
Case #ComStatus_ErrorOpenPort
Text = "ComError: Open Port " + *ComData\Port
Case #ComStatus_ErrorSend
Text = "ComError Send: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\SendError)
Case #ComStatus_ErrorReceive
Text = "ComError Receive: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\ReceiveError)
Case #ComStatus_ErrorDataSize
Text = "ComError Send: Port " + *ComData\Port + " - Send data size to big."
EndSelect
If Bool(Text)
StatusBarText(0, 0, Text)
EndIf
EndProcedure
BindEvent(#My_Event_NewState, @MyEventNewStateCB())
; ---------------------------------------------------------------------------
Procedure InitComport()
With ComData
If \Status
ProcedureReturn 2 ; Always running
EndIf
\Port = "COM3"
\Baud = 19200
\Parity = #PB_SerialPort_NoParity
\DataBit = 8
\StopBit = 1
\Handshake = #PB_SerialPort_NoHandshake
\BufferSize = 2048
\EndOfText = #CRLF$
\StatusCB = @StatusCB()
\ReceiveCB = @ReceiveCB()
\ThreadID = CreateThread(@thComport(), ComData)
If Not \ThreadID
ProcedureReturn 0 ; Error create thread
Else
ProcedureReturn 1 ; ok
EndIf
EndWith
EndProcedure
; ---------------------------------------------------------------------------
OpenWindow(0, 0, 0, 770, 620, "Arduino Hava istasyonu", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
CanvasGadget(0, 10, 10, 750, 580)
CreateStatusBar(0, WindowID(0))
AddStatusBarField(#PB_Ignore)
AddWindowTimer(0, 1, 2000)
InitComport()
UseModule Graph
;{ init graphics
; 1000 olan nokta sayısı değişirse x ekseni daralır ya da genişler
*graph.Graph = Graph_Init(10, 20, 700, 200, 1000, "Temperature")
*ad_temperature = Graph_AddSerie(*graph, #Red, #True)
;*ad_cosinus = Graph_AddSerie(*graph, RGB(200, 200, 255), #True) ; 2saniye testi
*graph_humidity.Graph = Graph_Init(10, 290, 700, 200, 1000, "Humidity")
*ad_humidity = Graph_AddSerie(*graph_humidity, #Blue, #True)
;}
_dt_lim = 40 ; bunu değiştirince x ekseni daralır ya da genişler
time = ElapsedMilliseconds()
a=-3
; ---------------------------------------------------------------------------
Repeat
;{ event
Repeat
event = WindowEvent()
Select event
Case #PB_Event_Timer
If EventTimer() = 1
If a=-3 : a=3 : ElseIf a=3 : a=-3 : EndIf
EndIf
EndSelect
If event = #PB_Event_CloseWindow
If ComData\Status
ComData\Exit = 1
EndIf
End
EndIf
Until event = 0
;}
;{ fps ayarı
dt = ElapsedMilliseconds() - time
If dt < _dt_lim
Delay(_dt_lim - dt)
EndIf
dt = ElapsedMilliseconds() - time
time = ElapsedMilliseconds()
;}
Graph_AddPoint(*ad_temperature, ValF(temperature$) ) ;Cos(2* 3.14 * time))
;Graph_AddPoint(*ad_cosinus, a) ; 2saniye testi
Graph_AddPoint(*ad_humidity, ValF(humidity$))
;{ chart çizimi
StartDrawing(CanvasOutput(0))
Box(0, 0, OutputWidth(), OutputHeight(), 0)
Graph_Draw(*graph)
Graph_Draw(*graph_humidity)
If maxTemperature < ValF(temperature$) : maxTemperature = ValF(temperature$) : EndIf
If maxHumidity < ValF(humidity$) : maxHumidity = ValF(humidity$) : EndIf
DrawText(150, 540, "Max. Temperature : " + StrF(maxTemperature,2) , #Green, 0)
DrawText(450, 540, "Max. Humidity : " + StrF(maxHumidity,2) , #Green, 0)
StopDrawing()
;}
ForEver
Bu grafiği görebilmek kolay olmadı. Çocukların cama resim yapmak için yaptıkları gibi sensöre biraz hohlamam gerekti. 😄
Siteyi, About sekmesinden başlayarak okuyun. Yazıları da sırayla okuyun. Aksi halde önceki yazılarda belirttiğim püf noktaları kaçırmış olursun.
VB veya C# .Net ile karşılaştırmak gerekirse PB’de kodlar biraz uzuyor. Ama daha anlaşılır olarak görüyorum ben. .Net’de herşeyin kütüphanesini bulmak mümkün, bazı basit işlemler bile PB’de kodlama yapılarak çözülüyor. Böylece konuyu daha temelden anlamış oluyorsun. Forumda hertürlü kodu bulabiliyorsun. Bulamasanız bile foruma sorabiliyorsun.
PB’in güzelliği de şu ki, programın, .Net veya Java’da olduğu gibi başka bir kütüphaneye bağımlı olarak değil tamamen kendi kendine çalışıyor olmasıdır. Yukarıdaki kodları alın, PB editörüne yapıştırın. Program çalışacaktır. İşletim sistemi Windows 7, 8, 9, 10, 11 olsa da bu kod hepsinde çalışır. Windows 9 diye birşey olmadığını biliyorum 😄 Neden 8’den 10’a atladılar ? Bu soru hep beynimi tırmalamıştır 🧗
PB’in bir başka güzelliği de thread konusunun sade ve basit olmasıdır. Örneğin bu işi .Net ile yapsaydık, seri porttan gelen bilginin grafiğe veya forma aktarılması için Delegate, Invoke gibi komutlar kullanmak gerekecekti. .Net için thread konusunu araştırırsan, çok karmaşık ve zor olduğunu görürsün. Sadece thread konusunu anlatan .Net kitaplarının yazıldığına şahit olabilirsin. PB’de ise thread, çok çok daha basit ve anlaşılır..