rsta2 / circle

A C++ bare metal environment for Raspberry Pi with USB (32 and 64 bit)

Home Page:https://circle-rpi.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TCP Connection in Interrupt

d-marson opened this issue · comments

Hello,

We have found your project to be a great tool for experimenting and learning, so first of all, thank you for making this available!

We are trying an exercise to trigger a TCP connection with a button press (connected via GPIO pins) and would like to use an interrupt to do so. We have been able to successfully set up an interrupt handler and use it to blink an LED; after the LED blinks, control is handed back to the main loop as expected. However, when we swap out the LED code for code that tries to establish a TCP connection, the device seems to hang when it tries to execute the Connect method of CSocket. No connection is detected and no messages are written to console.

We tested our TCP connection code within the main loop and our server was able to receive connections, so we're not sure what the issue may be when we're running it within the interrupt handler. Below is our kernel.cpp code; we appreciate any suggestions you may have!

#include "kernel.h"
#include <circle/string.h>

#include <circle/sched/task.h>
#include <circle/net/in.h>
#include <circle/net/netsubsystem.h>
#include <circle/net/socket.h>
#include <circle/net/ipaddress.h>

// Network configuration
#define USE_DHCP

static const char FromKernel[] = "kernel";

CActLED CKernel::ActLED; // initialize LED

CKernel::CKernel (void)
:	m_Screen (m_Options.GetWidth (), m_Options.GetHeight ()),
	m_Timer (&m_Interrupt),
	m_Logger (m_Options.GetLogLevel (), &m_Timer),
	m_GPIOManager (&m_Interrupt), 		// try to set up GPIO manager
	m_GPIO18 (18, GPIOModeOutput), 		// access to GPIO pin 18 for LED
	m_GPIO17 (17, GPIOModeInput), 		// access to GPIO pin 17 for button
	m_USBHCI (&m_Interrupt, &m_Timer)
{
	ActLED.Blink (5);		// show we are alive
	m_GPIO18.Write (LOW); 	// initialize as off
}

CKernel::~CKernel (void)
{
}

boolean CKernel::Initialize (void)
{
	boolean bOK = TRUE;

	if (bOK)
	{
		bOK = m_Screen.Initialize ();
	}

	if (bOK)
	{
		bOK = m_Serial.Initialize (115200);
	}

	if (bOK)
	{
		CDevice *pTarget = m_DeviceNameService.GetDevice (m_Options.GetLogDevice (), FALSE);
		if (pTarget == 0)
		{
			pTarget = &m_Screen;
		}

		bOK = m_Logger.Initialize (pTarget);
	}

	if (bOK)
	{
		bOK = m_Interrupt.Initialize ();
	}

	if (bOK)
	{
		bOK = m_Timer.Initialize ();
	}

	if (bOK)
	{
		bOK = m_GPIOManager.Initialize ();
	}

	if (bOK)
	{
		bOK = m_USBHCI.Initialize ();
	}

	if (bOK)
	{
		bOK = m_Net.Initialize ();
	}

	return bOK;
}

TShutdownMode CKernel::Run (void)
{
	m_Logger.Write (FromKernel, LogNotice, "Compile time: " __DATE__ " " __TIME__);

	// Test LED
	m_GPIO18.Write (HIGH); 
	m_Timer.MsDelay (1000); 
	m_GPIO18.Write (LOW); 

	// Get IP
	CString IPString;
	m_Net.GetConfig ()->GetIPAddress ()->Format (&IPString);
	m_Logger.Write (FromKernel, LogNotice, "Starting up server object.");
	ActLED.Blink (1);

	// Set up GPIO pin interrupt
	unsigned int myPin = 17;
	CGPIOPin myInputPin (myPin, GPIOModeInput, &m_GPIOManager);

	// Connect pin interrupt
	myInputPin.ConnectInterrupt (foo, this);
	myInputPin.EnableInterrupt (GPIOInterruptOnFallingEdge);

	while (1)
	{

		// // Write to console
		// m_Logger.Write (FromKernel, LogNotice, "Attempting repeating connection.");
		
		// // Socket
		// CSocket *pSocket = new CSocket (&m_Net, IPPROTO_TCP);
		
		// // IP
		// u8 TargetIPArray[] = {192, 168, 1, 100};
		// CIPAddress ForeignIP(TargetIPArray);
		// u16 nForeignPort = 5001;
		
		// // Connect
		// pSocket->Connect (ForeignIP, nForeignPort);
		
		// // Send
		// CString message(" test message \r\n\r\n");
		// unsigned messageLength = message.GetLength();
		// pSocket->Send (message, messageLength, MSG_DONTWAIT);

		// // close connection
		// delete pSocket;

		// m_Logger.Write (FromKernel, LogNotice, "Attempt completed.");

	}

	return ShutdownHalt;
}

void foo ( void *pParam )
{

	CKernel *pThis = (CKernel *) pParam;
	pThis->myInterruptHandler();

}

void CKernel::myInterruptHandler ()
{

	// Write status
	m_Logger.Write (FromKernel, LogNotice, "Entered Interrupt.");

	// Socket
	CSocket *pSocket2 = new CSocket (&m_Net, IPPROTO_TCP);
	
	// IP
	u8 TargetIPArray2[] = {192, 168, 1, 100};
	CIPAddress ForeignIP2(TargetIPArray2);
	u16 nForeignPort2 = 5000;
	
	// Connect
	pSocket2->Connect (ForeignIP2, nForeignPort2);
	
	// Send
	CString message2(" interrupt message \r\n\r\n");
	unsigned messageLength2 = message2.GetLength();
	pSocket2->Send (message2, messageLength2, MSG_DONTWAIT);

	// close connection
	delete pSocket2;
	
	// write status
	m_Logger.Write (FromKernel, LogNotice, "Exit Interrupt.");

}

This behavior is intended. The networking functions have to run in a task context at TASK_LEVEL, because they may block, while waiting for some event from the network. In an IRQ handler at IRQ_LEVEL the execution cannot block, because an IRQ handler has to be finished as soon as possible, so that the system remains responsive on other arriving IRQs.

As a solution you can set a volatile flag in the IRQ handler, when the button is pressed, which is read in the main loop. When in the main loop the flag is read as set, the network connection is established there. The flag has to be reset then.

Thanks for appreciating Circle!

Thank you for the rapid and detailed response, this is very helpful for our understanding!