细说linux信号 一

l         什么是信号

信号是一个消息,用来进行进程间的相互通信,这和人们用e_mail相互通信类似。所以信号的处理一定是在某一进程中进行的。

信号的发生是由于某一事件而引起的,这些事件可以是:1、由硬件产生的硬件异常(比如除零操作),2、由软件产生信号(比如先前设置的闹钟时间到时),3、从终端产生的信号,4、使用kill函数。

Linux下有三十多种的信号,不同的信号其默认的处理是不一样的,对于一些无关紧要的信号,进程在接收后不做任何处理(比如:当子进程的状态发生改变时向父进程发送的SIGCHLD信号),另外一些信号可能导致进程停止当前的操作转而执行其它的操作(比如:当你按下CTRLC时,SIGINT信号被送往当前进程,进程在接收到该信号后立刻终止运行)。

很多时候我们并希望使用系统默认的信号方式(比如:在一款游戏软件中,我们希望当进程接收到SIGINT信号后并不直接退出,而是先保存当前的数据然后在退出,这样当用户下次执行游戏时就能从上次被停止的地方继续执行),linux提供了许多和信号处理相关的函数,这些函数使得进程对信号的处理变得相当的灵活。

l         最简单的信号函数

首先解释下什么叫做信号句柄(signal handler):函数和变量一样都是有地址的,我们说函数的地址是指该函数在内存中的起始位置,编译器把这个地址告诉cpu,以便cpu找到该函数并执行。所谓信号句柄就是当进程接收到某一信号后执行那个函数的地址。它告诉进程当这个信号到来是应该运行那个函数。

void (*signal (int signo, void (*func)(int))) (int )

如果该函数调用成功将返回前次设置的信号句柄,否则返回SIG_ERR

signal函数设置当进程接收到某一信号(singno)时所执行动作。func 可以是a) 常量SIG_IGN(忽略该信号), b) 常量SIG_DEF(使用系统默认的处理方法),c)一个函数的地址(当信号到达时系统将转而执行该函数)。

当进程调用exec家族的函数后,所有信号都将被置为SIG_DEL或者SIG_IGN(这是由于exec改变了进程中代码段和数据段中的内容,所以原先设置的信号句柄在这时可能有其它的意义了)。

当进程调用fork函数产生子进程时,原先对信号的设置将被子进程所继承(因为子进程原样复制了父亲进程的代码段和数据段中的数据,或者它们共享相同的地址空间(COW),这取决与操作系统的实现)。

此外从<signal.h>头文件我们可以看到如下定义:

#define SIG_ERR (void (*) ())-1

#define SIG_DEL (void (*) ()) 0

#define SIG_IGN (void (*) ()) 1

这些宏告诉我们如何将一个常量转换为函数指针

l         不可靠的信号(unreliable signal

早期的操作系统对信号的处理存在着一个问题:当某信号发生是,系统将还原对该信号的设置。于是有人写下面这段代码:

int sig_int()/* my signal function*/

main(){

signal(SIG_INT,sig_int);

}

sig_int(int signo){

       signal(SIG_INT,sig_int);

      

}

乍看起来这里是不会有问题的,因为当进程第一次收到SIG_INT并执行sig_int句柄后虽然系统还原了对SIG_INT的默认处理(结束进程),但是由于进程再次调用了signal函数而使得当下个SIG_INT到来时进程递归进入下个sig_int句柄(而不是退出该进程)。

不幸的是这段代码是错误的,原因是忽略了窗口时间(window of time ),所谓窗口时间是指从信号发生到在信号句柄内(sig_int函数)调用signal函数前的这段时间。如果这时恰好进程又收到一个SIG_INT信号,由于信号系统默认的SIG_INT是终止进程,于是该进程将被终止。

                     早期系统存在的另外一个问题是我们无法关闭一个信号(阻止该信号出现), 能做的只有忽略(SIG_IGN)该信号。

l         可重入函数(reentrant function

在信号句柄中我们无法告诉进程该信号到来前一刻程序究竟运行到了哪个位置。假设信号到来前进程正通过malloc分配一个地址空间,并且信号句柄中我们再次调用了malloc,这时会发生什么事呢?由于malloc会修改一个保存空闲地址的链表,而且这个链表是全局数据类型,于是当第一个malloc正在修改这个链表时(修改未完成),由于一个信号到来,进程转而去执行信号句柄,而信号句柄中又调用malloc函数,第二个malloc也修改同一个链表,这样就产生了错误的数据。类似malloc的就是不可重入的函数,其它的就叫做可重入函数。

       判断一个函数是否为一个不可重入函数一帮按照下面标准:1、使用静态数据结构,2、有调用mallocfree函数,3、是标准I/O库的一部分(因为大部分的标准I/O库使用了全局数据结构)。

       通过调用sigsetjmp函数(以后将会说明)可以避免不可重入函数所产生的问题。

 

 

<think>我们正在讨论如何使用Pixhawk4通过手柄控制舵机和推进器,特别是映射通道的详细内容。根据之前的信息,映射通道涉及两个主要方面:手柄的物理通道与Pixhawk输出通道之间的映射,以及这些通道如何控制舵机和推进器。 关键点: 1. 手柄的每个摇杆或按钮都会输出个通道信号(例如通道1、通道2等)。 2. Pixhawk通过参数将手柄的通道映射到特定的功能(例如舵机或推进器)。 步骤: 1. 确定手柄通道:首先在QGC中校准手柄,确定哪个摇杆或按钮对应哪个通道。 2. 设置舵机和推进器的输出通道:将舵机连接到Pixhawk的AUX输出口(例如AUX1),推进器(电调)连接到MAIN输出口(例如MAIN1)。 3. 通过参数设置功能映射: - 舵机:设置个AUX通道为舵机功能,并映射到手柄的某个通道。 - 推进器:设置个MAIN通道为推进器(电机)功能,并映射到手柄的油门通道。 具体参数: 1. 舵机映射: - 假设舵机连接在AUX1(对应SERVO6,因为Pixhawk4的AUX1到AUX6分别对应SERVO6到SERVO11) - 设置参数`SERVO6_FUNCTION`,将其指定为舵机功能。通常,舵机功能可以设置为0(默认禁用)或特定的RC映射。根据ArduSub,我们可以指定为`7`(表示RCIN7映射)或者更简单的,直接设置为`0`(禁用自动控制,仅由RC控制)?但根据之前文档,我们使用`7`表示由RC输入通道7控制。 - 但更准确的是,根据ArduSub文档,舵机应该设置为`RCIN`映射。实际上,参数`SERVO6_FUNCTION`可以设置为`RCIN7`(即数字7),表示这个舵机由手柄的通道7控制。 - 同时,我们需要通过`RC_MAP_AUX1`参数将手柄的某个通道(比如通道7)映射到虚拟的AUX1输出。注意:`RC_MAP_AUX1`参数指定了手柄的哪个通道控制AUX1功能。所以,如果我们将`RC_MAP_AUX1`设为7,那么手柄通道7就会控制AUX1,而AUX1又通过`SERVO6_FUNCTION=7`(或者更准确地说,设置`SERVO6_FUNCTION=0`然后通过映射?)实际上,在ArduSub中,舵机输出通常使用`SERVOn_FUNCTION`设置为0(表示由手动RC控制)或者设置为特定的功能编号(如33表示电机,7表示RCIN7)。但为了明确由RC控制,我们可以设置为0,但是这样就需要通过通道映射来指定。 重新梳理: 在ArduSub中,输出通道的功能由`SERVOn_FUNCTION`参数决定。例如: - 设置`SERVO1_FUNCTION=33`表示MAIN1通道输出推进器(电机)控制信号。 - 设置`SERVO6_FUNCTION=0`(或者不设置,因为默认是0)表示该通道作为舵机,由RC输入直接控制。但是,具体由哪个RC输入通道控制呢?这实际上是由`RC_MAP`参数组决定的。 实际上,舵机的控制通道映射有两种方式: 方式1:直接映射 将舵机输出通道(例如SERVO6)直接绑定到某个RC输入通道。在参数中,我们使用`RC_MAP_AUX1`参数(注意:这里AUX1对应的是第6个输出通道之后的第个,即AUX1对应SERVO6)来指定手柄的哪个通道控制它。例如,设置`RC_MAP_AUX1=7`,那么手柄通道7就会直接控制SERVO6的输出。 方式2:通过功能映射 如果我们设置`SERVO6_FUNCTION`为个特定的功能(如7,表示RCIN7),那么该舵机就会由手柄通道7控制,而无需设置`RC_MAP_AUX1`。但这种方式在ArduSub中较少使用。 根据ArduSub文档[^1],更常用的方式是使用`RC_MAP`参数组来映射。 因此,我们采用以下步骤: 1. 设置舵机连接的物理输出端口(例如AUX1,对应SERVO6)的功能为0(默认,即手动舵机控制): ``` SERVO6_FUNCTION = 0 ``` 这样,这个端口就由RC输入直接控制。那么具体由哪个RC输入通道控制呢?它由其对应的`RC_MAP_AUXn`参数决定。对于AUX1,参数是`RC_MAP_AUX1`。 2. 设置`RC_MAP_AUX1`为手柄的某个通道(例如通道7): ``` RC_MAP_AUX1 = 7 ``` 这样,手柄通道7的值就会直接输出到SERVO6。 3. 设置推进器(电机)的映射: 推进器通常连接到MAIN端口。假设推进器接在MAIN1(对应SERVO1),则设置: ``` SERVO1_FUNCTION = 33 # 33表示电机 ``` 然后,将手柄的油门通道映射到电机的控制。油门通道通常是通道3(摇杆垂直方向),但我们可以通过`RC_MAP_THROTTLE`参数来设置。然而,在ArduSub中,推进器(油门)的映射是通过设置`RC_MAP_THROTTLE`参数来指定手柄的哪个通道控制油门。默认情况下,油门是通道3,但如果我们想用手柄通道1控制油门,则: ``` RC_MAP_THROTTLE = 1 ``` 这样,手柄通道1的值就会控制电机的转速。 4. 设置舵机和推进器的PWM范围: - 舵机:通过`SERVO6_MIN`和`SERVO6_MAX`设置舵机的最小和最大PWM值(单位微秒)。 - 推进器:通过`MOT_PWM_MIN`和`MOT_PWM_MAX`设置推进器的最小和最大PWM值。同时,还可以设置`MOT_SPIN_MIN`和`MOT_SPIN_MAX`(归化的油门值)来控制最小和最大转速。 5. 手柄校准:在QGC中,进入设置->操纵杆,校准手柄并确保每个通道的移动范围正确(通常为-100%到100%)。 6. 测试:在安全的条件下测试舵机和推进器的响应。 注意:手柄的通道值范围通常为1000到2000微秒(中心1500),但舵机和推进器可能需要不同的范围。例如,舵机可能需要1100到1900,推进器可能需要1100到1900(根据电调校准而定)。 因此,完整的映射流程总结如下: 手柄通道(摇杆) --> 通过`RC_MAP_*`参数映射到功能 --> 输出通道(SERVO) 其中: - 油门功能(推进器)由`RC_MAP_THROTTLE`参数指定手柄通道,并输出到设置为电机功能的输出通道。 - 舵机功能由`RC_MAP_AUXn`参数指定手柄通道,并输出到设置为舵机功能的输出通道(默认0)。 现在,我们详细说明每步: 步骤1:手柄校准 - 连接手柄到运行QGC的电脑。 - 在QGC中,进入“设置”->“操纵杆”,点击“校准”按钮,按照提示移动所有摇杆和按钮。 - 校准完成后,移动摇杆,观察通道值变化,记录下你想用于舵机和推进器的通道号(例如,油门通道为1,舵机控制通道为7)。 步骤2:硬件连接 - 舵机信号线连接到Pixhawk4的AUX1(对应SERVO6)。 - 推进器电调信号线连接到Pixhawk4的MAIN1(对应SERVO1)。 步骤3:参数设置(在QGC的参数设置界面) - 设置舵机通道: ``` RC_MAP_AUX1 = 7 # 手柄通道7控制AUX1(即SERVO6) SERVO6_FUNCTION = 0 # 设置为0,表示舵机由RC输入直接控制 SERVO6_MIN = 1100 # 舵机最小PWM(根据舵机规格调整) SERVO6_MAX = 1900 # 舵机最大PWM ``` - 设置推进器通道: ``` SERVO1_FUNCTION = 33 # MAIN1设置为电机输出 RC_MAP_THROTTLE = 1 # 手柄通道1作为油门通道 MOT_PWM_MIN = 1100 # 推进器最小PWM(根据电调校准调整) MOT_PWM_MAX = 1900 # 推进器最大PWM # 可选:设置死区等 THR_DZ = 25 # 油门死区(防止摇杆回中时微小变化导致电机转动) ``` 步骤4:测试 - 在QGC的“Vehicle Setup”->“Motors”界面,可以单独测试推进器(注意安全,移除螺旋桨)。 - 舵机测试:直接摇动手柄通道7对应的摇杆,观察舵机转动。 注意事项: - 解锁:在测试推进器之前,通常需要解锁。ArduSub解锁需要满足条件(例如油门最低,并且按下特定按钮组合)。可以在QGC中设置解锁方式。 - 安全:测试推进器时务必确保安全,最好将推进器固定并移除螺旋桨。 引用: [^1]: ArduSub官方文档提供了RC输入和输出的基本映射原理。 </think>### 使用 Pixhawk4 映射通道控制舵机与推进器的详细说明 在通过手柄控制舵机和推进器时,通道映射是核心环节。根据 ArduSub 的架构[^1],映射流程分为三个层级,以下是详细说明: #### 1. **层级映射结构** 手柄控制信号需经过三层映射才能驱动执行器: ```mermaid flowchart LR A[手柄物理通道] --> B[RC输入通道] --> C[功能通道] --> D[物理输出端口] ``` #### 2. **具体映射步骤** 1. **手柄通道 → RC输入通道** 在 QGC 中设置 `RC_MAP` 参数组: ```bash RC_MAP_ROLL = 1 # 通道1→横滚(可选) RC_MAP_THROTTLE = 2 # 通道2→推进器油门[^1] RC_MAP_AUX1 = 3 # 通道3→舵机控制 ``` - 验证:在 QGC 的 **Analyze > MAVLink Inspector** 查看 `RC_CHANNELS` 数据流 2. **功能通道 → 物理输出端口** 设置执行器绑定关系: ```bash SERVO1_FUNCTION = 33 # MAIN1端口→推进器(33=Motor) SERVO6_FUNCTION = 7 # AUX1端口→舵机(7=Manual Control) ``` - 数学映射关系: 手柄输入值 ($I$) 到 PWM 输出 ($P$) 的转换: $$ P = P_{\min} + (I + 100\%) \times \frac{P_{\max} - P_{\min}}{200\%} $$ 其中 $I \in [-100\%, 100\%]$, $P \in [P_{\min}, P_{\max}]$ 3. **输出范围限定** - 推进器参数: ```bash MOT_PWM_MIN = 1100 # 最低转速PWM MOT_PWM_MAX = 1900 # 最高转速PWM MOT_SPIN_MIN = 0.15 # 最小归化转速[^1] ``` - 舵机参数: ```bash SERVO6_MIN = 1100 # 舵机最小角度PWM SERVO6_MAX = 1900 # 舵机最大角度PWM ``` #### 3. **信号流验证方法** 1. **手柄原始信号** 在 Linux 终端执行: ```bash jstest /dev/input/js0 # 查看原始手柄数据[^1] ``` - 正常范围:各通道值 `-32767` 到 `32767` 2. **Pixhawk 接收信号** 在 QGC 的 **MAVLink Console** 输入: ```bash listener RC_CHANNELS ``` - 检查通道值是否在 `1100-1900μs` 范围 3. **输出信号测量** - 用示波器检测 MAIN1/AUX1 引脚 PWM 波形 - 期望波形:周期 20ms (50Hz),脉宽与参数匹配 #### 4. **典型故障排除** | 现象 | 检查点 | 关键参数 | |-----------------------|-------------------------------|-----------------------| | 舵机无响应 | `SERVOn_FUNCTION` 赋值 | `SERVO6_FUNCTION=7` | | 推进器不转 | 解锁组合键+油门死区 | `THR_DZ=25`[^1] | | 执行器动作反向 | 反向参数设置 | `SERVO6_REVERSED=1` | | 手柄信号丢失 | USB 连接稳定性 | `jstest` 指令验证 | > **重要提示**:修改参数后需重启飞控,执行器测试前务必遵守安全规范[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值