重要的变量、数据结构及功能函数

1. 变量

变量名 变量类型 存储区域 作用
eLedState enmLSType 全局变量 记录 LED 及数码管的当前状态
ledsta uint32 静态局部变量 奇数: 开对应的 CAN 收发灯
偶数: 关所有的 CAN 收发灯
CanLedStatus uint8 静态局部变量 开灯时具体开哪些 CAN 收发灯
uDispPos uint8 静态局部变量 0: 显示个位
1: 显示十位
LedTimer uint32 静态局部变量 LED 状态设置时复位,似乎后面没有定时应用
eBoardState _BOARD_STATE 全局变量 记录模块的当前工作状态
Flash_Data uint8[1024] 全局变量 无 Bootloader: 从最后扇区(0x0f)读取配置信息
有 Bootloader: 从 Bootloader 的最后一个扇区(0x05)读取配置信息
__Cfg Cfg_t 全局变量 模块配置信息
__DefCfg Cfg_t 全局常量 模块默认配置信息
__DaqCanIf canif_t * 全局 CAN 接口指针 指向可用的 CAN 接口
DeviceStatus uint8 静态局部变量 记录当前设备状态
DeviceStatusBak uint8 静态局部变量 记录设备上次的状态,暂没理解到该变量作用
DevStaData uint8[5] 静态局部变量 状态包向应数据暂存
SndCnt uint32 全局变量 CAN 发送计数

2. 数据类型

c
hal.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef enum _LED_STATE{
LS_XZ8802_ERR, /* 加密芯片错误状态 */
LS_INIT_FAIL, /* 初始化失败状态 */
LS_ID_CONFLICTS, /* 模块地址冲突状态 */
LS_ID_OVERRUN, /* 地址超限状态 */
LS_BOARD_TYPEERR, /* 设备类型错误状态 */
LS_CFG_XZ8802, /* 配置状态 */
LS_NORMAL, /* 正常工作状态 */
LS_BOOT /* 进行Bootloader状态 */
}enmLSType;

#define DEV_STA_LQD 0 /* 冷启动 */
#define DEV_STA_RQD 1 /* 热启动 */
#define DEV_STA_YJGZ 2 /* 硬件故障 */
#define DEV_STA_RJGZ 3 /* 软件故障 */
#define DEV_STA_ZJZC 4 /* 自检正常 */
#define DEV_STA_ZJCW 5 /* 自检错误 */
#define DEV_STA_DZCX 6 /* 地址超限 */
#define DEV_STA_DZCT 7 /* 地址冲突 */
#define DEV_STA_LXCW 8 /* 类型错误 */
#define DEV_STA_RJFW 9 /* 软件复位 */

c
main.c
1
2
3
4
5
6
7
enum   _BOARD_STATE{
BS_SEND_IDCMD, /* 发送地址检测命令状态 */
BS_WAIT_IDCMD, /* 待待接收地址检测命令响应状态 */
BS_WAIT_TIMEOUT, /* 超时未接收到地址检测命令响应状态 */
BS_INIT_OK, /* 初始化完成状态 */
BS_RCV_IDCMD /* 接收到地址检测命令响应状态 */
}eBoardState;
c
awx_daocha.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct
{
uint32 Ver; /* 固件版本 */
uint32 Date; /* 固件日期 */
uint32 Range; /* 转换量限 */
uint32 ACLinearRange; /* 交流线性转换范围,范围内的电压进行线性补尝 */
uint32 DCLinearRange; /* 直流线性转换范围 */
uint32 ModulusArray[MAX_AI_CHANNEL]; /* 标定系数 */
int32 ZeroPointArray[MAX_AI_CHANNEL]; /* 零点补尝 */
int32 LinearArray[MAX_AI_CHANNEL]; /* 线性补尝 */
uint32 OverrunRange; /* 电压超限范围 */
uint32 OverrunCnt; /* 超过电压超限范围的次数 */
uint32 ACChangeRange; /* 交流电压变化范围 */
uint32 DCChangeRange; /* 直流电压变化范围 */
uint32 ChangeCnt; /* 超出电压变化范围的次数 */
uint32 EnableFillDCDY; /* 允许填充直流电压 */
uint32 Addr; /* 设备地址 */
}Cfg_t;
c
CanIf_Driver.h
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
typedef struct canif
{
#if MULTI_CAN_IF_EN
struct canif *next; /* 多个CAN接口时,把所的CAN接口设备组成一个单向链表 */
#endif
uint32 CanErrCnt:8; /* 错误计数 */
uint32 CanSndColseCnt:8; /* 发送关闭计数 */
uint32 CanRcvEventEnable:3; /* 接收事件全能控制 */
uint32 CanLomModeEnable:1; /* 只听模式使能 */
uint32 InitBaud:4; /* 初始化波特率,用于自动波特率配置时判断是否尝试完所有波特率 */
uint32 WorkBaud:4; /* 实际使用的波特率 */
uint32 SetBaud :4; /* 波特率配置功能设置的新波特率 */
baud_t CanBaud; /* 波特率对应的寄存器配置参数 */
uint32 (*ioctl) (struct canif *canif,canif_io_type_t type,void *setval); /* 模式设置函数指针 */
int (*output) (struct canif *canif,CanMsg_t *msg); /* 发送函数指针 */
int (*input) (struct canif *canif,msg_buf_t *pBuf); /* 接收函数指针 */

enum {
idle, /* 空闲 */
wait_snd_finish, /* 等待发送完成 */
wait_dly_resnd /* 延时重发 */
}snd_sta; /* 发关状态 */

void *pSndBuf; /* 发送缓冲区指针 */
uint32 reSndTimerCnt; /* 重发送定时器 */
uint32 ContinuousSndFrames; /* 连续发送帧数 */
uint32 SndIntervalMs; /* 发送间隔时间 */

struct {
void *head; /* 列表头 */
void *end; /* 列表尾 */
uint32 cnt; /* 列表数量 */
}txbuf_list; /* 发送缓冲区列表 */

#if DELY_SEND_CAN_MSG
struct {
void *head;
void *end;
uint32 cnt;
}dely_txbuf_list; /* 延时发送列表 */
#endif

struct {
void *head;
void *end;
uint32 cnt;
}rxbuf_list; /* 接收缓冲区列表 */

void *flag; /* 自定义参数 */
uint32 BroadAddr; /* 模块地址 */
}canif_t;

typedef struct
{
unsigned char SJW; /* 同步段长度 1~4 */
unsigned char BS1; /* 时间段1长度 1~16 */
unsigned char BS2; /* 时间段2长度 1~16 */
unsigned int fq; /* 1/CAN时间单元 */
}baud_t;

typedef enum
{
SetBauad,
SetTx,
SetRx,
Stm32_SetFilter
}canif_io_type_t;

3. 功能函数

3.1. 数据码管显示函数

  • 函数原型: void DispAddr(uint32 addr)
  • 控制数据管显示模块地址或错误代码
  • 每调用一次该函数,就会在个位和十位显示间切换一次

DispAddr

mermaid流程图源码
plaintext
1
2
3
4
5
6
7
8
9
flowchart LR

if1{参数add小于\nDISP_ERROR_CODE_BASE} --> |Y| if2{静态变量\nuDispPos==0}
if2 --> |Y| id1(uDispPos=1) --> id2(显示模块地址个位) --> e(返回)
if2 --> |N| id3(uDispPos=0) --> id4(显示模块地址十位) --> e
if1 --> |N| if3{静态变量\nuDispPos==0}
if3 --> |Y| id5(uDispPos=1) --> id6(显示错误代码标识E) --> e(返回)
if3 --> |N| id7(uDispPos=0) --> id8(显示错误代码值) --> e

将数码管的查值表放在函数内外及定义成常量和非常量的区别:
图 1
图 2
图 3
图 4
到底是将查值表放在函数内还是函数外好,待分析对比。

3.2. led 状态设置函数

  • 函数原型: void SetLedStatus(enmLSType LSType)
  • 根据参数状态,设置每个 led 灯的开、关及闪状态。如果是错误数据管显示错误代码
LED 错误指示 错误代码 Error Work CAN1_Tx CAN1_Rx
加密芯片验证错误 E0
自检错误 E1
地址冲突 E2
地址超限 E3
类型错误 E4

3.3. 简单的软件定时器

说是软件定时器,其实就是一个整形变量。
该变量为值为0表示未启用该定时器,非0表示启用该定时器。
定时器复位,就是将 OS 的 Tick 值赋值给该变量。
定时器停止,就是将该变量值归零。
判断定器是否超时,就是比较 OS 当前的 Tick 值与定时器复位时的 Tick 值之差是否大于大于等于需要定时的值。

这种软件定时器,简单方便可以直接放在任务中,不必单独创建一个软件定时任务。
但也容易出现高优先级任务导致低优先级任务中的软定时器超时而得不到响应。所在多任务间的协作要注意

3.4. 读取模块配置信息

  • 函数原型: int ReadConfigFromEeprom(uint32 addr,uint8 *dst,int len,uint8 *def)
  • 函数功能: 从配置区的开始地址处读取指定长度的配置信息,如果读取失败就恢复默认配置

读配置

有 Bootloader 时,配置信息放在 Bootloader 的最后一个扇区(0x05)Flash 地址为 0x5000
无 Bootloader 时,配置信息放在 Flash 的最后一个扇区(0x0f)Flash 地址为 0xf000
在整合配置扇区(4k)空间内,应用程序配置信息从 0x100 处开始,前 256 字节(0x00-0xFF)预留给 Bootloader 用

mermaid绘图源码
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
flowchart LR
id1(从Flash配置区中\n应用配置区末尾\n读出CRC值)
id2(从Flash配置区中\n应用配置区\n读取全部配置)
if1{配置信息\nCRC校验}
if2{是否给了\n默认配置}

id1 --> id2 --> if1
if1 --> |通过| e1(返回\n配置数据长度)
if1 --> |不过| if2
if2 --> |有默认配置| id3(将默认值\n写入应用配置区) --> e2(返回\n0)
if2 --> |无默认配置| id4(将配置数据全归0) --> e3(返回\n-1)

3.5. 发送 CAN 数据帧

  • 函数原型: int canif_start_send(canif_t *canif)
  • 函数功能: 从 CAN 接口的发送队列中取取 1 帧数据并发送
flowchart TB
if1{是否\n指定CAN\n接口}
if2{发送功能\n是否关闭}
if3{接口\n是否空闲}
if4{发送指针\n是否为空}

TaskStart 任务

TaskStart

mermaid流程图源码
plaintext
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
flowchart TB

%% subgraph TaskStart
%% direction TB

id1(喂看门狗) --> id2(设置 led 状态为正常状态) --> id3(加密芯片检测)

id3 -->id4(创建 TaskAwxCp 任务)
id4 --> id5(任务延时 5ms)
id5 --> if1{判断\n100ms 定时器\n 是否超时}
if1 --> |N| if2{led 是否\n 为正常状态}
if2 --> |Y| id6(数码管显示模块地址) -->id5
if2 --> |N|id5

subgraph 利用系统 Tick 延时 100ms
%% direction LR
if1 --> |Y| id7(复位 100ms 定时器) --> id8(再次喂狗)
id8 --> if3{led 是否\n 为正常状态}
if3 --> |Y| id9(根据静态变量\nledsta 和 CanLedStatus\n 开关 CAN 收发灯)
id9 --> id10(关 led 告警灯)
if3 --> |N| id11(更新 led 状态)
end

id10 --> if2
id11 --> if2

%% end

  • 任务优先级为:2
  • 任务堆栈深度为:512
  • 该任务主要完成正常状态下的地址显示和 CAN 收发灯开关

TaskAwxCp 任务

任务流程图

CAN 状态框图

CAN 数据解析流程图

需要解析的命令列表

命令功能 帧类型 命令内容 备注
进入 Boot 模式 标准帧 0xFF , 0xAA , 址址 , 类型 , 随机数 , CRC 检验
取设备历史数据 扩展帧 地址 , 0x03 , 寄存器地址高 , 寄存器地址低 , 数据长度高 , 数据长度低
熄灭设备工作灯 扩展帧 0x01 , 0x10
点亮设备工作灯 扩展帧 0x01 , 0x11
设备类型与分不匹配 扩展帧 0x01 , 0x0A
设备地址超分机配置 扩展帧 0x01 , 0x0B
设备地址冲突 扩展帧 0x01 , 0xFF
取最新的数据 扩展帧 0x01 , 0x61 交流表示电压
取最新的数据 扩展帧 0x01 , 0x64 直流表示电压
读设备配置信息 扩展帧 0x01 , 0x04 | 0x01 , 地址 , 寄存器地址 ,数据长度
写设备配置信息 扩展帧 0x01 , 0x05 | 0x02 , 地址 , 寄存器地址 ,数据 1 , 数据 2 , 数据 3 , 数据 4 数据为小端

mermaid绘图源码
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
flowchart LR

subgraph 设备初始化
direction TB
id1(初始化ADC\n并创建TaskAI任务)
id2(读取模块\n配置信息)
id3(获取模块地址)
id4(初始化CAN接口)
id5(设置全局CAN接口指针\n__DaqCanIf)

id1 --> id2 --> id3 --> id4 --> id5
end

id6(根据500ms定时器\n闪烁工作指示灯) --> id7(根据CAN状态\n收发CAN数据)-->id6
设备初始化 --> |通过| id6
设备初始化 --> |不过| 设置初始化失败状态

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
stateDiagram-v2
state "发送地址检测命令状态" as BS_SEND_IDCMD
state "待待检测命令响应状态" as BS_WAIT_IDCMD
state "超时未接收到数据状态" as BS_WAIT_TIMEOUT
state "初始化成功状态" as BS_INIT_OK
state "地址冲突状态" as BS_RCV_IDCMD
state if1 <<choice>>

[*] --> BS_SEND_IDCMD: 启动发送地址检测
BS_SEND_IDCMD --> BS_WAIT_IDCMD: 发送地址检测命令包
BS_WAIT_IDCMD --> if1
if1 --> BS_RCV_IDCMD: 收到地址冲突帧
BS_RCV_IDCMD --> BS_SEND_IDCMD: 定时器超2s
if1 --> BS_WAIT_TIMEOUT: 未收到地址冲突数据包\n且定时器超时2s
BS_WAIT_TIMEOUT --> BS_INIT_OK
BS_INIT_OK --> BS_INIT_OK: 解析正常数据包
BS_INIT_OK --> BS_SEND_IDCMD: 解析到地址冲突数据包

plaintext
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
flowchart LR

id1(收到数据包)
if1{帧类型}
id2(进入Boot模式)
if2{"Data[0]"}
e(返回)

subgraph Data1["Data[1]"]
direction LR
sif1{0x10} --> |相等| sid1(灭工作灯)
sif2{0x11} --> |相等| sid2(亮工作灯)
sif3{0x0A} --> |相等| sid3(显示错误码E4)
sif4{0x0B} --> |相等| sid4(显示错误码E3)
sif5{0xFF} --> |相等| sid5(显示错误码E2)
sif6{0x61} --> |相等| sid6(返最新交流道表数据)
sif7{"0x04\n0x01"} --> |相等| sid7(读配置)
sif8{"0x05\n0x02"} --> |相等| sid8(写配置)
end

id1 --> if1 --> |标准帧| id2
if1 --> |扩展帧| if2
if2 --> |== 0x01| Data1
if2 --> |!= 0x01| e
Data1 --> e

任务序列图

任务序列

mermaid绘图源码
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
sequenceDiagram
Note over TaskStart: 优先级: 2
Note over TaskAwxCp: 优先级: 1
Note over TaskAI: 优先级: 3
TaskStart ->> TaskAwxCp: 创建任务TaskAwxCp<br/>并切换至该任务运行
TaskAwxCp -->> TaskAI: 创建任务TaskAI
TaskAwxCp ->> TaskAwxCp: 工作灯闪<br/>处理CAN接口数据包
TaskAwxCp ->> TaskStart: TaskAwxCp任务延时
TaskStart ->> TaskStart: 控制CAN收发灯闪<br/>显示模块地址
TaskStart ->> TaskAI: TaskStart任务延时
TaskAI ->> TaskAI: 模拟量采集并存储
TaskAI ->> TaskAwxCp: TaskAI任务延时

值得学习和借鉴之处

  1. 线程内通过简单软件定时器实,LED 灯定时闪烁。我之前创建了一个单独的软件定时器线程,有需要定时的就创建一个软件定时器。软件定时器线程的优先级还要比其它应用线程的优先级高,这样保证了软件定时器回调函数能尽可能的及时调用。但线程的开销会更大。
  2. 模块配置信息的读写添加了 CRC 检验。