原版 arm_linux_program 程序分析

Glossary

  • RCV: 电台接收机
  • RCU: 主控?
  • XTU:电台发射机
  • FPP: 429 / 153 / 422 所有外部接口
  • ICMU:综合信息处理机
  • HF: High frequency 高频(短波电台)
  • RTU: 通过 429 / 1553 / 网络(udp) 连接电台的外部设备。传输控制信息。
  • SFU: 通过 422 连接电台的外部设备。传输数据链。
  • FPP 电台程序里对外部接口。
  • 业务 ⇒ ICMU 422 的上层协议。SLIP协议(Serial Line IP,串行线路IP协议,见RFC1055)封装。
  • 波形(RCV) ⇒ 业务 422 的下层协议。
  • 40 ⇒ 41, 42: 422 协议里的源和目的 ID。
  • KJT:开机通(?),包含一些旧的定频通信等实现。

Analyze

Macro Controlled conditional compile

#ifdef BOARD_IS_MDM_41R00C_V3 // defined in GCC / G++ Config #define CPU_6416 #define MAX_CH 2 //发送解调通道的数目 #define MAX_CH_XMITTER 1 //发射机数量 #define MAX_CH_RECEIVER 1 //接收机数量 #define USE_KJT_AND_JBZ //军兵种网内应用&&开机通业务 lgw@2015-8-18 18:27:10 #endif

Main: rcu_linux_main.cpp

argv[1] 传入 dbg_xmt_type //0:aisc 7:rf board 8:io bc 9:dsp 10:HSSI E0 11:ARM 207_2 12:NET 13:SIM 15:io board。ARM 板子为 7。2: debug mode main: OS_TASK_START_ALL(); // 启动各个内部工作线程(pthread) AppNet_LogStart(); // log AppNetAud_TskStartUp(); //207 (FPGA) receive thread 接口 OS_TASK_START_ALL(); // 启动所有线程。 task 管理 OS_EXT_ARM_LINUX.c #define MAX_TSK_NUM 32 pthread_t mOSTskTbl [MAX_TSK_NUM] ; // 存放每个 task 的 pthread id int mTskMsgQid[MAX_TSK_NUM] ; // 每个task 一个 msg queue int mTskMsgErr[MAX_TSK_NUM] ; // 各个task错误码 int mTskMsgMsOut[MAX_TSK_NUM]; // timeout。每个task的定时器 OS_TSK_MBX_INIT(); // 初始化各线程的 System V message queue (msgget) OS_EEP_Init(; // os_eep_c_arm_linux.c, 读取电台工作参数表配置文件。位置: ./eep, savefile_A.bin, savefile_B.bin OS_Reg_StartUp(); // NOOP OS_HW_init_sio(); // 初始化 serial io (系统各串口设备) COM1-COM32 (主要是初始化缓冲区) stHwSIO[1].BufTxQue, BufRxQue / BufPtrStHwSioST_HW_SIO OS_HW_init_frame(); // ST_FRAME stFrame[2];. 每个stFrame对应1个串口和 32 frames * 256 bytes 缓冲区。COM30 (29) / COM31 (30)。保密机(bmj)同步串口 OS_HW_init_rtc(); // FOR_MDM_2300 and RTC_DS3234: yes. RTC_DS1302: not. DS3234 RTC芯片。#define COM_RTC 26 OS_StartTimer(); // setitimer /SIGALRM 设置的 2000μs interval 定时器. signal_timer_handler OsTaskCreateP(ID_DSP); // DSP //#if USE_KJT_AUTO_TEST==1//lgw@2020/3/18 13:56:15 add,1 msgque 2share memory OsTaskCreateP(ID_RCV) ;// Task_App_Rcv. RCU: 大部分主控功能 // rcu_xmit 发射流程状态机。4个功率(db)等级 0,6,3,12 /* 20*2个状态机 AppRcuTsk_Xmit_Manager_01 // idle 状态。 检测键控(>=0) => 02 xmit_Manager_02 : 更新发射机频率和业务(sub_xmit_load_new_freq) 发射中心频率 + 频偏 _03: 检测频率合法 =>6, 非法 => 4 6: 记录上一次频率(1stStGrpChan) 以备失败时恢复上次频率 7: noop 8: 对于电台,关闭接收机,关闭喇叭 9: fsub_xmit_strt_xmit 正式启动发射.设置 task_xmit_main 状态机为 5 10:设置发射机已经启动状态 11: 等待ptt释放 => stop_xmit 结束发射流程 15: 常态,正常发 rcutsk_xmit_main : 主发射流程状态机 4: idle 5: 检测频率合法,非法 => warning 6-9 : exception 10: set_ctrl_set_channel_ptt_on 打开信道 ptt => 207 set_filt_ptt_onoff 设置滤波器开关 sub_ctrl_key_pow_amplifier */ // rcu_recv 接受流程 // rcu_ptt.c PTT管理。 // LPC 声码化 /* sub_ptt_manager : 模拟业务下的ptt状态机管理 sub_get_ptt_krq_status 5ms 去抖 Sub_Read_Krq_Ext 读取硬件 PTT 状态(电位) // 不同板子实现不同。最终通过 207 获取 sub_ptt_manager_lpc : 管理声码化业务模式下的 ptt sub_ptt_manager_proc */ //#endif OsTaskCreateP(ID_MAG) ;// Task_App_Mag。APP_MANAGER OsTaskCreateP(ID_FPP) ; // Task_App_Fpp, 429 / 253 / 422 等所有外部接口 // MsgQueue.cpp 发送各种消息 OsTaskCreateP(ID_RCU) ;// Task_App_Rcu。电台接收机管理 OsTaskCreateP(ID_START) ;// TaskStart 打印其它 task 状态后推出

207 app_net_aud_207.c

#define MAX_CH_RCV_CTL 40 typedef struct { ST_JESD_H h ; // 207 header SET_RCV_FIXF set_rcv ; }ST_CHXX_RCV_CTRL; ST_CHXX_RCV_CTRL stChxxRcvCtrl ;//xx通道接收控制 SET_RCV_FIXF stChAllRcvCtl[MAX_CH_RCV_CTL] ;//40通道接收控制 AppNetAud_TskStartUp(): init_chnl( CH4,NAME_CH4_b,AppNetAud3_callback_aud,chntag ) // 注册 207 回调函数 AppNetAud3_VarInit(); OsTaskCreateExt((void*) thread_net_Aud3_Proc ); // 207 音频接收线程 AppDspAud_Init_8k_to_32k();//TBD AppNetAud3_Init_Xmt_Ctrl();// 发射机 AppNetAud3_Init_Rcv_Ctrl();//init 接收控制 AppNetAud3_SubSendPack( AUD3_MSG_DN_RCV_FIXF,sizeof(SET_RCV_FIXF),(AUD3_ST_JESD *)&stChxxRcvCtrl); // 设置定频 // INT8U AppNetAud3_SubSendPack( INT32U msgID, INT32U len,AUD3_ST_JESD* p ) AppNetAud3_Init_Filt_Arg();//jy @2021/11/11 15:37:23 add AppNetAud3_Init_Alc_Arg ();//jy @2021/11/11 15:37:23 add thread_net_Aud3_Proc() { while( 1 ) { AppNetAud3_ProcInTsk(); // FPGA发给主控的(麦克风等输入)音频数据流 msleep(2); } }

Tasks

每个 Task thread: * 循环用 msgrcv (blocking)读取该 task 的消息队列,当收到新消息时,执行该 task 的主函数。主函数里常常是一个状态机,根据收到的消息更新状态。

Message Mechanism

// 消息机制由 Task_Mag 定义并管理。app_msg_tsk.cpp #define STATUS_MAN_OPER 0x0110 // 272 // app_msg_id1.h // 程序自定义消息 msgId #define MSG_RADIO_STATE 0x0500 /* APP_MSG: GeneralMsg */ #define MSG_QRY_RADIO_STATE (MSG_RADIO_STATE | 0x01) /* 查询电台状态 */ #define MSG_CHG_RADIO_STATE (MSG_RADIO_STATE | 0x02) /* 报告|更改电台状态 */ app_fpp/app_msg_que/MsgQueue.cpp 定义了一个发送消息类。将各种操作命令(比如更改或查询工作模式)封装为发送消息: ChgRadioState(STATUS_MAN_OPER);//更改电台状态为定频 其 PutMsg 最终调用 msgsnd 给 MAG (ID_MAG = 8) 这个 task 的消息队列发消息。("OS"消息,即 FPP => MAG,通过 blocking 的 System V 消息队列接收),发送消息指定 ch 通道号 = 0。 app_mag_tsk.cpp 里的 APP_MANAGER_PROC_MSG_OS 接收并处理这些消息。回复的消息放到 modules => mag 应用层消息队列(MsgQueFm_Modu)的(回复消息的ch相同)。 OSQPendExt / OSQPost : 接收(blocking) / 发送System V 消息。 // app_mag_msg.c 定义了"应用层的消息队列",MAG 内部使用。共10个通道。 // moduxx: module 。全局变量。 // MsgQueTo_Modu[MAX_DIV_CH] 子任务接收消息队列。来源于 MAG 收到的 OS 消息。 void PostMsgTo_Moduxx_manager( APP_MSG *ptrMsg ,INT16U ch ) // mag => 子任务 消息 INT16S PendMsgTo_Moduxx_manager( APP_MSG *ptrMsg ,INT16U ch ) // 读 mag => 子任务 的消息 // MsgQueFm_Modu[MAX_DIV_CH] 子任务发送消息队列。由 mag 读取后默认转发给 FPP。 INT16S PendMsgFm_Moduxx_manager( APP_MSG *ptrMsg ,INT16U ch ) // mag 读 子任务发的消息。none-blocking,返回 1表示成功并将msg放到全局变量里。 void PostMsgFm_Moduxx_manager( APP_MSG *ptrMsg ,INT16U ch ) // 子任务 => mag. 消息(放到队列)。 子任务指 mag 下属的任务:MAN / ALE / AUTO / HOP / DATA / Hnet / Div / Net / KJT。对应不同电台工作模式。不同任务通过 bit flag 区分。APP_MANAGER_MAIN 里根据优先级找到第一个bit flag 已 set的子任务并执行其。但实际写的逻辑有很多问题。 // Task_App_Mag() while( tagStartTaskReady ) { OSQPendExt( ID_MAG,1,(OS_Msg *)&msgToMag ) ; // 读取系统消息 TaskAppMagMain(); } TaskAppMagMain() { APP_MANAGER_PROC_MSG_OS(&msgToMag) ;//来自OS的消息处理。用OSQPost回复其它模块。 => //这里不处理的消息,下发到子任务(eg. TASK_NET_MAIN)处理 PostMsgTo_Moduxx_manager(p, ch); APP_MANAGER_MAIN() ;//执行子任务 APP_MANAGER_PROC_MSG_MODU() ;//统一发送子任务回复消息给各个模块(MAG => FPP) } APP_MANAGER_MAIN() { // each 0-1 channel: // 每一个 channel 当前会有 active task: check now_task_active_ch[ch] & BIT_TSK_NET // 执行当 active 的子任务的 main 函数。 // now_task_active_ch[ch] 由 OS 消息 MSG_CHG_RADIO_STATE 控制设置为对应子任务。 => App_Task_Net / App_Task_KJT / App_Task_Man // 每个子任务对应一个 ch =>Task_Net_Main / Task_KJT_Main / Task_Man_Main(ch) // 主要上报当前信道状态等消息处理 => msg = PendMsgTo_Moduxx_manager(ch); // 处理 } APP_MANAGER_PROC_MSG_MODU() { msg = PendMsgFm_Moduxx_manager(ch) // 0-1 chnnel 消息依次处理。 // AppDspSlip_NewMsgToOpt(msg) // SLIP 封装消息 // 2-9 channel 消息依次处理 // AppDspSlip_NewMsgToOpt(msg) // 转发给前面板。 // 最后: // 通过 OSQPost 转发给 FPP。MAG => FPP } // MAG => FPP 的消息在 FPP 模块线程中处理

Task ↔ Task Interfaces (Messages)

FPP ⇒ MAG
Parameters
Reply (MAG⇒FPP)
Parameters
备注
MSG_CHG_RADIO_STATE
MSG_CHG_RADIO_STATE
GeneralMsg.status >> 8) && 0xff == TAG_WORK_MODE_MAN
设置(更改)工作模式
MSG_QRY_RADIO_STATE
MSG_CHG_RADIO_STATE
同上
查询电台状态。回复的电台状态报告用 MSG_CHG_RADIO_STATE。
MSG_TRAFFIC_CHANGE
MSG_TRAFFIC_CHANGE
更改业务类型。FIX_ANLG_VOICE / FIX_DGTL_VOICE。原样回复 ACK

Task_Fpp

// app_fpp_slip_714.cpp, app_rcu_sn.h Task_App_Fpp() { while( tagStartTaskReady ){ OSQPendExt(ID_FPP, 1,&msgToFpp ); //ms TaskAppFppMain(); } } // AppKJT_Main 和 AppNET_Main 等函数位于 lib_arm_kjt.a 里。 TaskAppFppMain() { m_cFppSlip.AppFppSlip_Main(); // AppFppSlip_ParseSpeech_Synthesis() 语音合成 m_cFppSlip.AppFppSlip_GetMsg( &msgToFpp ); // 处理 OS 消息 // KJT & jbz AppKJT_Main(&msgToFpp) ;//lgw@2015/9/14 18:48:39 for KJT AppNET_Main(&msgToFpp) ;//lgw@2018/10/18 14:26:08 for JBZ NET AppNetKJT_Eep_TrafMemory_Main(&msgToFpp) ;//lgw@2019/5/25 13:10:06 add AppNetKJT_Eep_Contact_Memory_Main(&msgToFpp);//lxz add 20190625 } AppFppSlip_GetMsg(msg) { // 根据不同消息类型,调用 Slip 封装向不同串口发送外部消息(状态报告等) // 封装方式。协议未知 // C0 A0 Byte(dataLen+7) 42 40 Byte(ctrlWord) Bytes(data) CRC(data)(2 bytes) C0 AppFppSlip_SendToSlip(comId, ctrlWord, data, dataLen); }

HW_sio (串口)

// os_hw_sio_arm.c // comID, BufTxQue, BufRxQue; lenTxQueN1, lenRxQueN1 为缓冲区大小 - 1 // 使用循环指针 ptrTxQueII , ptrTxQueOO, ptrRxQueII, ptrRxQueOO // II: input (向缓冲区输入新数据), OO: output (从缓冲区读取数据) // tx 发送 // sub_sio_get_string_xmt(comID, *buf, len) // 读取所有待发送的数据 // OO 指向tx buf里待发送的数据位置。II 指向tx buf 最后写入的数据位置。 // ptrTxQueOO 为 当前待发送的tx数据缓冲区位置(0 based offset) // 当前逐 byte 发送。如果已到BufTxQue末尾,移回开头(p = p & (len -1))。 // 可优化 // sub_sio_get_len_oque() // 获取当前待发送的 tx 数据长度。 // (ptrTxQueII - ptrTxQueOO) & (len -1) // 结果按 unsigned 对待。根据补码和溢出原理计算实际长度。需要 len 是 2 的倍数。 // sub_sio_get_len_ique() // 获取当前待读取的 rx 数据长度 // rx 接收 // sub_sio_put_string_rcv(comId, data, len) // 硬件串口上读到新数据 // ptrRxQueII++ // sub_sio_get_char(comID), sub_sio_get_string(comId, buf, len) // 应用程序从 rx buf 读取数据 // ptrRxQueOO++ ST_HW_SIO stHwSIO[32]; // 32 个串口tx/rx缓冲区buf Main() => OS_HW_init_sio() => OS_HW_init_sio_isr() => OsTaskCreateExt(thread_proc_sio_send) // tx 数据是单独线程轮询发送的。代码里发送sio时只用 sub_sio_put_char 写数据写入tx buf。 void *thread_proc_sio_send(void *data) { while(tagStartTaskReady) { OSTimeDlyMs(8); proc_sio_tx(COM4); proc_sio_tx(COM8); proc_sio_tx(COM10); proc_sio_tx(COM12); #ifdef FOR_11F04 // current is 11F06 proc_sio_tx(COM32); #endif } } char buf[8] 0 1 2 3 4 5 6 7
串口
外部设备
备注
COM4 (3)
保密机
COM8
前面板通信串口TTL
COM10
外置天调
COM12
前面板14芯串口
COM32
推125W天调串口

自检

app_rcu_check_11f06.c 自检 AppRcu_Check 发送消息要求自检

New

S1 ↔ S2 Interface
APP_FPP ProcTrafMsg 通过系统的消息队列接收。