从函数间通信看待程序设计

程序设计的一个重要方面,是处理过程划分。
这是结构化的基本要求。

将处理过程划分为一个个片段,这就是结构化之后形成的函数。
函数的基本特征就是,接受一定的输入数据,经过处理后,产生一定的输出数据。
这其中的核心,就是函数间通信。

函数间通信,发生在调用者和被调用者之间,也发生在上游函数调用和下游函数调用之间,也发生在前台ISR函数调用和后台函数调用之间,不一而足。

每一次函数调用,会拥有自己的一个局部变量区域,位于stack之中,通常称为stack frame。
局部变量,包括了入口参数,return,内部定义的局部变量。

调用者和被调用者通信,就需要填充被调用者的stack frame中的entry param区域,
另一方面,被调用者的return variable,则被复制到调用者的局部变量中,或者由pointer所指定的位置。
可以称之为variable transfering。

除此之外,其他形式的函数间通信,都需要用到一种通信方式,即variable sharing。
而variable sharing,则是本文讨论的重点。

在SOD或者OOD设计中,
variable sharing是推荐的设计思想。

设计static变量,是一种variable sharing。
但是一旦数量庞大,则管理起来将是一件困难的事情。
为此,我们需要设计struct,将相关的variable封装起来,便于管理。
当系统内存在大量的struct后,管理起来又是一件困难的事情,
为此,我们需要设计pointer,将相关的struct关联起来,便于索引需要的数据。
这就是SOD的基本设计思想。

随着函数数量的增加,如果仅仅使用SOD设计,则函数管理起来,仍然是一件困难的事情。
为此,我们需要设计class,一方面,将相关的variable封装起来,一方面,将相关的函数也封装起来。当发生函数调用时,系统能够找到正确的代码段。

总之,主要以variable sharing为主的函数间通信方式,
成为SOD设计或者OOD设计的必然选择。

同一个class中定义的成员函数,它们之间的通信方式,推荐是variable sharing方式。
不同的class中定义的成员函数,它们之间的通信方式,推荐也是variable sharing方式。

相关的对象,会用一些指针把他们相互关联起来,形成一定的数据拓扑。
这通常是数据结构需要解决的问题。
例如形成链式拓扑,树状拓扑,网状拓扑等。

在variable sharing的大环境下,
当需要发生函数调用时,首先需要考虑的问题,就是,数据对象的起点在哪?
对于SOD而言,就是首参,首参一般是一个结构体对象。
对于OOD而言,就是class object,这是默认的首参。

函数调用在执行的过程中,要么会从sharing variable中读取数据到自己的局部变量中,要么会用自己的局部变量中的数据,更新sharing variable。
这些sharing variable,通常都被设计为成员变量。
这样,当另外的函数调用在执行时,只要给定了对应的对象,自然能够找到正确的sharing variable。

也就是说,以同一个对象为首参而发生的不同的函数调用,函数间通信方式,最佳的方式,就是基于成员变量的共享式通信。

++++++++++++++++++++++++++++++++++++++++++++++++++
下面列举几种常见的程序设计框架。
++++++++++++++++++++++++++++++++++++++++
前后台架构
以uart_lite为例,
由于接收过程,是异步的,只能以事件驱动的方式处理,当uart接收到数据后,触发中断作为event,前台callback函数来响应事件。将接收的数据从FIFO中转存到对应的buffer,后台函数在轮询处理时,从buffer中取出数据。

在注册callback时,同时注册了callbackref。
这是callback运行时,所需要的数据环境。
在SOD或者OOD设计时,callbackref通常是一个数据对象,以指针的方式提供。

void uart_lite_isr_handle(struct UartLiteDev* dev)
{
	u8 retRegister;
	u8 retRegisterVal;

	u32 regs = dev->regs;
	struct RingBuffer* recv_buff = &dev->recv_buff;
	
	retRegisterVal = XUartLite_GetStatusReg(regs);
	XUartLite_WriteReg(regs, XUL_CONTROL_REG_OFFSET, 0);

	while (1) {
		
		retRegister = XUartLite_GetStatusReg(regs);

		if (retRegister & XUL_SR_RX_FIFO_VALID_DATA) {
			ring_buffer_push(recv_buff, XUartLite_ReadReg(regs, XUL_RX_FIFO_OFFSET));
		}
		else {
			break;
		}
	}

	retRegisterVal &= XUL_CR_ENABLE_INTR;
	XUartLite_WriteReg(regs, XUL_CONTROL_REG_OFFSET, retRegisterVal);
}

从callbackref中,获取所需要的硬件资源数据,作为后续的硬件操作的依据,
在uart的callback中,读取FIFO中的数据,并转存到ringbuffer中。
这里,调用了ringbuffer的操作集。

在这个架构中,函数间通信,使用的sharing variable,就是callbackref所指定的数据对象。
以及其关联的buffer。

再来看看,对应的后台中,如何在轮询过程中,对buffer进行处理。
首先是轮询的主框架级函数,

SystemManager xsys;
int main()
{
	init_platform();

	xsys.open_device();
	
	xsys.main_loop();

	cleanup_platform();
	return 0;
}

这里,定义了全局数据对象xsys,作为系统级的数据对象,
在,main中,inti业务,是由open_device函数来完成的。其中调用了大量的功能级函数实现整板的硬件资源的init和数据对象的inti。
之后,main_loop函数,就是主框架函数了。

void SystemManager::main_loop()
{
	boot_state_ = 1;
	xout_.set_sdi_reset(1);

	watchdog_start();
	while(1)
	{
		watchdog_feed();

		xmotor_.recv_loop();
		xhost_.recv_loop();

		xaec_.compute();

		motor_loop();
		sensor_loop();
		
	}
}

在这个主框架中,使用了while(1)循环体,实现永续循环,这是轮询处理的主体代码块,
在这个代码块中,调用了多个业务级函数,依次完成业务处理。

来看看recv_loop这个业务级函数的业务逻辑。

void MotorMcu::recv_loop()
{
	demux(&uart_->recv_buff, &mcu_cmd_);
	mcu2fpga_cmd_parse(&mcu_cmd_);
	mcu2fpga_cmd_action(&mcu_cmd_);
}

函数中,调用了三个功能级函数,
demux需要使用ringbuffer,整个关联拓扑是,this对象关联到UART结构体对象,UART结构体对象又关联到ringbuffer结构体对象。demux以this对象作为数据对象起点,可以索引到ringbuffer。
这里,OOD设计的优势得到了体现。合理设计对象之间的关联和拓扑,就能够逻辑清晰地完成数据交易。

demux对ringbuffer进行接收,并将接收的对齐的数据帧,存放到cmd结构体对象中。

之后,调用mcu2fpga_cmd_parse函数,对cmd对象进行解析处理,
cmd对象的设计,使用了数据设计的一种设计思想,即变量化散列。将数据帧中的未符号化(未实义化)的数值,解析后,填充到已经符号化(实义化)的成员变量中。在后续的处理时,直接使用成员变量,可以更逻辑清晰的设计流程代码,使代码可读性更好。

void mcu2fpga_cmd_parse(struct MCU2FPGACommand* mcu_cmd)
{
	struct MCU80msCommand* mcu80ms_cmd = &mcu_cmd->mcu80ms_cmd;
	struct MCUTestCommand* mcutest_cmd = &mcu_cmd->mcutest_cmd;
	struct MCUIrisCommand* mcuiris_cmd = &mcu_cmd->mcuiris_cmd;
	mcu80ms_cmd_parse(mcu80ms_cmd);
	mcutest_cmd_parse(mcutest_cmd);
	mcuiris_cmd_parse(mcuiris_cmd);
}

这个函数,调用了更具体的功能级函数来进行解析。
以其中一个为例,进行说明。

void mcutest_cmd_parse(struct MCUTestCommand* mcutest_cmd)
{
	int ret;
	if (!mcutest_cmd->fresh)
		return;

	mcutest_cmd->fresh = 0;
	mcutest_cmd->action = 0;

	ret = check_xor_crc(&mcutest_cmd->data[2], 6, mcutest_cmd->data[8]);
	if (ret < 0)
		return;

	unsigned char* pdata = (unsigned char*)&mcutest_cmd->data[0];

	mcutest_cmd->action = 1;

	mcutest_cmd->filter_ok = pdata[7] & 0x1;
	mcutest_cmd->iris_ok = (pdata[7] >> 1) & 0x1;
	mcutest_cmd->zoom_ok = (pdata[7] >> 2) & 0x1;
	mcutest_cmd->focus_ok = (pdata[7] >> 3) & 0x1;
}

可以看出,该函数中,完成了变量化散列的过程,将未实义化的数据帧,实义化为cmc对象中的成员变量的值。

之后,调用mcu2fpga_cmd_action函数。

void MotorMcu::mcu2fpga_cmd_action(struct MCU2FPGACommand* mcu_cmd)
{
	struct MCU80msCommand* mcu80ms_cmd = &mcu_cmd->mcu80ms_cmd;
	struct MCUTestCommand* mcutest_cmd = &mcu_cmd->mcutest_cmd;
	struct MCUIrisCommand* mcuiris_cmd = &mcu_cmd->mcuiris_cmd;
	
	mcu80ms_cmd_action(mcu80ms_cmd);
	mcutest_cmd_action(mcutest_cmd);
	mcuiris_cmd_action(mcuiris_cmd);
}

这个函数,调用了更具体的功能级函数来进行解析。
以其中一个为例,进行说明。

void MotorMcu::mcutest_cmd_action(struct MCUTestCommand* mcutest_cmd)
{
	if (!mcutest_cmd->action)
		return;
	mcutest_cmd->action = 0;

	filter_ok_ = mcutest_cmd->filter_ok;
	iris_ok_ = mcutest_cmd->iris_ok;
	zoom_ok_ = mcutest_cmd->zoom_ok;
	focus_ok_ = mcutest_cmd->focus_ok;

}

可以看出,该函数中,完成了变量化散列的过程,将cmd对象中已经实义化的成员变量值,复制到this对象的对应的成员变量中。

到目前为止,可以看到,业务级函数recv_loop,只是完成了相关数据对象的成员更新,并没有涉及到硬件处理。

void SystemManager::motor_loop()
{
	static u32 tv0 = 0;
	u32 tv1 = get_timestamp();
	u32 dlt_time = ABS_DEC(tv0, tv1);
	if (dlt_time < 35) {
		return;
	}
	tv0 = tv1;


	switch(zoom_state_)
	{
		case 1:
		{
			set_dzoom_scale_inc(0);
			if (get_dzoom_scale_state() == 2) {
				xmotor_.set_zoom_up(zoom_speed_);
			}
		}break;
		case 2:
		{
			xmotor_.set_zoom_down(zoom_speed_);
			if (xmotor_.get_optics_focus_state() == 2) {
				set_dzoom_scale_dec(0);
			}
		}break;

	}

	switch(focus_state_)
	{
		case 1:
		{
			xmotor_.set_focus_far(focus_speed_);
		}break;
		case 2:
		{
			xmotor_.set_focus_near(focus_speed_);
		}break;
		default:
			break;
	}
}

在motor_loop中,根据数据对象中设置的flag,执行实际的动作。
以其中一个为例。

void MotorMcu::set_zoom_up(int speed)
{
	struct FPGA2MCUCommand cmd;
	
	fpga2mcu_cmd_init(&cmd);
	fpga2mcu_set_zoom_field(&cmd, 1, (u8)speed);
	fpga2mcu_cmd_calc_crc(&cmd);
	
	uart_lite_send(uart_, cmd.data, cmd.len);
}

该函数,构造具体的cmd结构体对象后,填充对应的成员变量, 之后,就通过UART发送出去了。

variable sharing的函数间通信方式,
贯穿了整个架构。

如果系统中需要添加其他的功能业务,那么只需要按照相似的框架,完成数据设计和处理设计,即可方便的实现。

每一个业务模块,使用自己的业务数据对象,在业务处理的流程中,各个函数,从sharing variable中读取所需的数据,处理后,再将自己的处理结果,写入sharing variable中,供后续的函数使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值