0%

组合功能机器软件

0、简述

单片机使用LPC1115,采用ucos-ii。

程序主要包括:1个主任务、3个功能任务,1个串口发送任务,1个状态指示灯任务,1个蜂鸣器任务。

功能任务就是实现机器主要功能的任务,与机器的应用场合有很大的关系,所以在此不做过分详细的说明。每个功能可以实现开启、暂停、结束等命令,实现方式在下面说明。

其它几个任务是辅助性的任务,为了更好地服务于机器功能的实现。

1、主任务

解析上位机的命令,并向任务发送命令。

主任务结构伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
static uint8_t queue_func_insert(struct andrdata* andr)
{
switch(andr->cmd)
{
case FUNCTION_CMD_PUMP:
{
OSQPost(q_pump, (void*)(andr_pump_buf + pump_write));
}
break;
// ......
default:
return 0;
}
}

static uint8_t func_insert(struct andrdata* andr)
{
if(queue_func_insert(andr) == 0)
return CMD_ERROR;

mbox_msg = OSMboxPend(from_task, OS_TICKS_PER_SEC / 50, &err);
code = *mbox_msg;
//根据返回结果处理

return code;
}

void task_main_func(void* pvData)
{
//......
while(1)
{
//......
while(1)
{
if(andr_recv_queue.is_empty())
break;

andr = andr_queue_get(&andr_recv_queue);
//......
code = func_insert(andr);
//......
}
}
}

主任务处理流程:

①解析上位机命令

②判断分析,如果合适,将命令发送至任务,等待任务应答。

这样处理意在将各功能任务与上位机命令分离,各部分相互独立,减弱耦合。

③主任务根据任务的应答进行后续处理。

2、功能任务

功能任务用来实现各个具体的功能,虽然因任务的不同而有所变化,但处理流程基本一致。代码框架如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
该函数用来在功能运行过程中,接收上位机的一些命令,
诸如暂停、恢复、停止等,并且在暂停过程中还有超时处理。
若有其它控制需求,还可以比较方便地增加。
*/
uint8_t wait_mbox2task(uint8_t index, uint16_t timeout, uint8_t* state, fun_call func, uint8_t para)
{
mbox_m = OSMboxPend(mbox, timeout, &err);
if(err != OS_NO_ERR)
return FUNC_NONE;

switch(*mbox_m)
{
case FUNCTION_SUSPEND:
{
OSMboxPost(md->mbox, &md->msg);
while(1)
{
mbox_m = OSMboxPend(mbox, OS_TICKS_PER_SEC, &err);
if(err == OS_NO_ERR)
{
switch(*mbox_m)
{
case FUNCTION_RESUME:
md->msg = ALARM_CMD_OK;
OSMboxPost(md->mbox, &md->msg);
return FUNC_NONE;

case FUNC_STOP:
//...
return FUNC_STOP;

default:
md->msg = ALARM_CMD_ERROR;
OSMboxPost(md->mbox, &md->msg);
break;
}
}

if(++suspend_time >= SUSPEND_TIMEOUT)
{
return FUNC_TIMEOUT;
}
}
break;
}

case FUNC_STOP:
//...
return FUNC_STOP;

default:
//...
break;
}
return FUNC_NONE;
}

void task_pump(void* pvData)
{
while(1)
{
//do something
andr = OSQPend(q_pump, OS_TICKS_PER_SEC, &err);
if(err != OS_NO_ERR)
continue;

//andr参数检查

while(1)
{
//事件更新与处理

ret = wait_mbox2task();
switch(ret)
{
case FUNC_STOP:
//..
goto TASK_STOP;

case FUNC_TIMEOUT:
//...
goto TASK_STOP;

default:
continue;
}
}

TASK_STOP:
stop_task(flag_stop, flag_deal);
}
}

在任务执行过程中,检查邮箱是否有数据到来:如果有,则分析具体动作,并执行之;否则就继续运行。命令包括几个方面:暂停(需要处理超时,不能长时间暂停)、恢复、停止(结束任务)等。

因不同任务有不同的特点,所以各个任务会有各自的处理细节。比如某个任务工作时,仅仅需要计时即可,当达到定时要求后结束执行即可。而另一个任务功能则需要计量液体体积,同时检测水桶的水位,如果水位低于警报值,那么需要提醒用户或者报警;另一方面,向水桶中注水时则需要关注高水位警报。

3、辅助任务

(1)串口发送任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void task_uart_send(void* pvData)
{
while(1)
{
us = OSQPend(q_uart_send, 0, &err);
if(err != OS_NO_ERR)
continue;

switch(us->com)
{
case UART_TO_RS232:
cmd_resp(us->UART_TO_RS485andr);
break;

case UART_TO_RS485:
for(i = 0; i < UART_SEND_COUNT; i++)
{
UARTSend(data, len);
OSSemPend(, , &err);
if(err != OS_NO_ERR)
continue;
//do something else
}
break;

default:
break;
}
}
}

/*
在其它位置调用下面的函数用以发送数据。
在本项目的实际应用中,不会有问题,但是这里仍不完善,有待改进。
*/
uint8_t mb_send_data(struct uart_send* us)
{
send_buf[send_write] = *us;
OSQPost(q_uart_send, (void*)(send_buf + send_write));
send_write = (send_write + 1) % Q_SIZE_SEND;
}

在程序的不同位置会向串口发送数据,其实只是发送到队列,在这个任务中处理这些待发送的数据。结合实际的应用需求,单片机UART0通过扩展芯片外接有两个设备:上位机、Modbus设备。上位机是rs232接口,Modbus设备是rs485接口,并且Modbus设备是从设备,所以在处理Modbus设备时需要处理数据应答与重发问题。

可以这样简单理解。数据有多个源头,但是均需要从同一个串口发送,那么将他们放在一个队列中,在一个任务中将数据一个一个地取出并发送。在发送任务中,根据数据的不同目的设备,通过RS232或者RS485发送输出。

691xg0.png

因为数据的接收也是从同一个uart进入,所以有可能丢失数据。比如在处理rs485设备时,rs232接口有数据过来,或者相反的情形。但是这种情况比较少,有几个原因:①上位机数据发送有比较大的时间间隔;②从rs485发送数据时是报警、开启停止任务的时候。但仍然存在数据丢失的可能性,所以才会有数据重发机制。

(2)状态指示与蜂鸣器

这里的处理与数据发送任务比较类似,数据来源于多个位置,但是均需要从同一个UART输出,所以使用队列的处理方式,在任务中从队列中取数据处理。

状态指示灯需要实现不同的效果,比如闪烁间隔、周期、次数等,各功能任务的状态显示均需通过状态指示任务处理,最后再通过串口发送任务发送数据。

还有一个报警功能。所谓的报警,就是通过状态指示灯与蜂鸣器来提醒用户,并且上位机在查询状态时,也会检查到报警状态并提示。

(3)空闲任务

电路板上有一个状态灯,将其放在空闲任务中,根据任务的不同状态,显示不同的状态。比如在空闲时为呼吸灯效果;有任务工作时,会是闪烁的状态;如果有报警,则以比较快的速度闪烁。

其实在各功能运行时,大部分时间都是阻塞的状态,所以空闲任务会运行大部分时间,将状态灯放在这里进一步减轻了主要任务的处理压力。

(4)其它细节
  • 任务优先级

    因为涉及到主任务与功能任务之间的通信以及串口数据发送问题,所以需要注意各个优先级的设置。在本项目中,功能任务优先级最高,主任务优先级次之,接着是串口发送任务,最后是蜂鸣器与状态指示任务。

  • 同时工作的任务数量限制

    这主要是实际应用的需求才这样设计,在上述代码中未体现。

    一个问题是如何记录正在工作的任务及其数量?这里使用一个字节位的方式来处理。如果某个功能正在运行,那么将相应的位置1,完成后清0,统计1的数量就可以得到正在工作的任务的数量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static uint8_t get_bits_count(uint8_t data)
    {
    uint8_t i;

    for(i = 0; data; i++)
    {
    data &= (data - 1) ;
    }
    return i;
    }

    当然,单片机RAM有限,不可能同时运行太多的任务。

  • 命令处理

    指通信协议方面,需要做一些处理,包括命令的解析与数据打包,命令的应答等。同时,也要注意不同设备状态下命令的处理,比如:任务已经打开,不允许再次打开操作,否则返回错误命令等。

  • 看门狗

    采用外部看门狗芯片,放在主任务中处理,当然也还有其它处理方式,但是我理解不应该放在定时器中,因为有可能程序已经死机,但是定时器仍在运行,起不到应有的作用。

  • 其它

    报警的标记与清除、交易记录、参数设置、数据保存与读取(互斥量保护)等方面的内容。

4、问题

(1)RS485接口速度

板子的rs232与rs485从同一个uart引出,但是对于rs485接口,当波特率超过9600,数据收发就会出现错误,但是RS232即使波特率到了115200也是没有问题的。

板子与485设备均在机器内部,长度可能也就几十公分,在开发、测试时长度更短,并且总线上只有这一个485设备。

一个原因是板子的设计问题,另一个是接地问题,还可能是软件处理收发方向的问题。这个需要后续进一步地验证。

(2)液体计量精度

在管路上有一个编码器,通过统计编码器脉冲数可以计算得到流过的液体体积。当然精度不是很高,一是没有高精度的流量计,二是安装方式也会有很大的影响。因为并没有拿到一个产品的精确参数,只能通过多次测试来得到比较精确地参数。

当然,本产品也不需要很高的精度,否则就需要更高精度的物理实现了。

5、总结

本文从应用的角度总结了项目的软件结构,整个软件还包括上位机通信协议设计,这是很重要的一部分。其实这里的上位机就是一个运行Android系统的屏,安装在机器上作为用户操作的界面,与它的通信通过RS232进行,Android屏作为通信的发起者,这里的单片机程序接收数据并应答。

整个项目从软件的设计、开发调试,到最后的测试,这个过程中遇到了一些问题,还有一些很低级的错误。但是因为涉及到多个部门,所以需要耐心沟通,慢慢调试,找出问题并解决之。