{"action":"created","comment":{"url":"https://api.github.com/repos/cleanflight/cleanflight/pulls/comments/47950342","id":47950342,"diff_hunk":"@@ -661,6 +661,130 @@ void StopPwmAllMotors()\n }\n \n #ifndef USE_QUAD_MIXER_ONLY\n+static int16_t decidegreeToServoPulse(uint8_t servo_index, int16_t angle_deci_degree)\n+{\n+    // normalize angle to range -180 to 180 (degree)\n+    while (angle_deci_degree > 1800)\n+        angle_deci_degree -= 3600;\n+    while (angle_deci_degree < -1800)\n+        angle_deci_degree += 3600;\n+\n+    //remap input value (RX limit) to output value (Servo limit), also take into account eventual non-linearity of the two half range\n+    if (angle_deci_degree > 0) {\n+        angle_deci_degree = scaleRange(angle_deci_degree, 0, servoConf[servo_index].angleAtMax * 10, 0, servoConf[servo_index].max);\n+    } else {\n+        angle_deci_degree = scaleRange(angle_deci_degree, 0, servoConf[servo_index].angleAtMin * 10, 0, servoConf[servo_index].min);\n+    }\n+\n+    return angle_deci_degree;\n+}\n+\n+int16_t getMix(uint8_t rule_index)\n+{\n+\n+    static int16_t currentOutput[MAX_SERVO_RULES];\n+\n+    uint8_t servo_index = currentServoMixer[rule_index].targetChannel;\n+\n+    // consider rule only if no box assigned or box is active\n+    if (!currentServoMixer[rule_index].box == 0 && !IS_RC_MODE_ACTIVE(BOXSERVO1 + currentServoMixer[rule_index].box - 1)) {\n+        currentOutput[servo_index] = 0;\n+        return rxConfig->midrc;\n+    }\n+\n+    uint16_t servo_width = servoConf[servo_index].max - servoConf[servo_index].min;\n+    int16_t min = currentServoMixer[rule_index].min * servo_width / 100 - servo_width / 2;\n+    int16_t max = currentServoMixer[rule_index].max * servo_width / 100 - servo_width / 2;\n+\n+    bool needScale = true;\n+\n+    int16_t responseRange; //in range [-500:500] if needScale = true, else is [min:max]\n+    int16_t input_from = currentServoMixer[rule_index].inputSource;\n+\n+    switch (input_from) {\n+        case INPUT_STABILIZED_ROLL:\n+        case INPUT_STABILIZED_PITCH:\n+        case INPUT_STABILIZED_YAW:\n+            //WARINING: this list of case works only if the order in rcData index enum is the same of inputSource_e\n+            if (FLIGHT_MODE(PASSTHRU_MODE)) {\n+                responseRange = rcCommand[input_from - INPUT_STABILIZED_ROLL];\n+            } else {\n+                responseRange = axisPID[input_from - INPUT_STABILIZED_ROLL];\n+            }\n+            // Reverse yaw servo when inverted in 3D mode\n+            if (rule_index == INPUT_STABILIZED_YAW && feature(FEATURE_3D) && (rcData[THROTTLE] < rxConfig->midrc)) {\n+                responseRange *= -1;\n+            }\n+            break;\n+\n+        case INPUT_STABILIZED_THROTTLE:\n+            responseRange = motor[0] - 1000 - 500;  // Since it derives from rcCommand or mincommand and must be [-500:+500]\n+            break;\n+\n+        case INPUT_RC_ROLL:\n+        case INPUT_RC_PITCH:\n+        case INPUT_RC_YAW:\n+        case INPUT_RC_THROTTLE:\n+        case INPUT_RC_AUX1:\n+        case INPUT_RC_AUX2:\n+        case INPUT_RC_AUX3:\n+        case INPUT_RC_AUX4:\n+            //WARINING: this list of case works only if the order in rcData index enum is the same of inputSource_e\n+            responseRange = rcData[input_from - INPUT_RC_ROLL] - rxConfig->midrc;\n+            break;\n+\n+        case INPUT_GIMBAL_PITCH:\n+            needScale = false;\n+            responseRange = decidegreeToServoPulse(servo_index, inclination.values.pitchDeciDegrees);\n+            break;\n+        case INPUT_GIMBAL_ROLL:\n+            needScale = false;\n+            responseRange = decidegreeToServoPulse(servo_index, inclination.values.rollDeciDegrees);\n+            break;\n+    }\n+\n+    //now, if the input, for coinvenience, has been mapped from [-500:500], we have to scale it to [min:max]\n+    if (needScale){\n+        if (responseRange > 0) {\n+            responseRange = scaleRange(responseRange, 0, 500, 0, max);\n+        } else {\n+            responseRange = scaleRange(responseRange, 0, -500, 0, min);\n+        }\n+    }\n+\n+    if (currentServoMixer[rule_index].speed != 0){\n+        if (currentOutput[rule_index] < responseRange)\n+            currentOutput[rule_index] = constrain(currentOutput[rule_index] + currentServoMixer[rule_index].speed, currentOutput[rule_index], responseRange);\n+        else if (currentOutput[rule_index] > responseRange)\n+            currentOutput[rule_index] = constrain(currentOutput[rule_index] - currentServoMixer[rule_index].speed, responseRange, currentOutput[rule_index]);\n+    }else{\n+        currentOutput[rule_index] = responseRange;\n+    }\n+\n+    responseRange = servoDirection(servo_index, input_from) * constrain(((int32_t) currentOutput[rule_index] * currentServoMixer[rule_index].rate) / 100, min, max);\n+\n+    return responseRange;\n+}\n+\n+STATIC_UNIT_TESTED void servoMixer(void)\n+{\n+    uint8_t i;\n+\n+    for (i = 0; i < MAX_SUPPORTED_SERVOS; i++)\n+        servo[i] = 0;\n+\n+    // mix servos according to rules\n+    for (i = 0; i < servoRuleCount; i++) {\n+        uint8_t servo_index = currentServoMixer[i].targetChannel;\n+        servo[servo_index] += getMix(i);\n+    }\n+\n+    for (i = 0; i < MAX_SUPPORTED_SERVOS; i++) {\n+        servo[i] = ((int32_t)servoConf[i].rate * servo[i]) / 100L;\n+        servo[i] += determineServoMiddleOrForwardFromChannel(i);\n+    }\n+}\n+/*\n STATIC_UNIT_TESTED void servoMixer(void)","path":"src/main/flight/mixer.c","position":128,"original_position":128,"commit_id":"13ce0cb213239d01732548243a555bbab5a0a439","original_commit_id":"13ce0cb213239d01732548243a555bbab5a0a439","user":{"login":"MauroMombelli","id":6372561,"avatar_url":"https://avatars.githubusercontent.com/u/6372561?v=3","gravatar_id":"","url":"https://api.github.com/users/MauroMombelli","html_url":"https://github.com/MauroMombelli","followers_url":"https://api.github.com/users/MauroMombelli/followers","following_url":"https://api.github.com/users/MauroMombelli/following{/other_user}","gists_url":"https://api.github.com/users/MauroMombelli/gists{/gist_id}","starred_url":"https://api.github.com/users/MauroMombelli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MauroMombelli/subscriptions","organizations_url":"https://api.github.com/users/MauroMombelli/orgs","repos_url":"https://api.github.com/users/MauroMombelli/repos","events_url":"https://api.github.com/users/MauroMombelli/events{/privacy}","received_events_url":"https://api.github.com/users/MauroMombelli/received_events","type":"User","site_admin":false},"body":"wops, dead code. note to self: remove","created_at":"2015-12-17T19:35:44Z","updated_at":"2015-12-17T19:35:44Z","html_url":"https://github.com/cleanflight/cleanflight/pull/1591#discussion_r47950342","pull_request_url":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591","_links":{"self":{"href":"https://api.github.com/repos/cleanflight/cleanflight/pulls/comments/47950342"},"html":{"href":"https://github.com/cleanflight/cleanflight/pull/1591#discussion_r47950342"},"pull_request":{"href":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591"}}},"pull_request":{"url":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591","id":54006522,"html_url":"https://github.com/cleanflight/cleanflight/pull/1591","diff_url":"https://github.com/cleanflight/cleanflight/pull/1591.diff","patch_url":"https://github.com/cleanflight/cleanflight/pull/1591.patch","issue_url":"https://api.github.com/repos/cleanflight/cleanflight/issues/1591","number":1591,"state":"open","locked":false,"title":"Servo angle for tilting, part 2","user":{"login":"MauroMombelli","id":6372561,"avatar_url":"https://avatars.githubusercontent.com/u/6372561?v=3","gravatar_id":"","url":"https://api.github.com/users/MauroMombelli","html_url":"https://github.com/MauroMombelli","followers_url":"https://api.github.com/users/MauroMombelli/followers","following_url":"https://api.github.com/users/MauroMombelli/following{/other_user}","gists_url":"https://api.github.com/users/MauroMombelli/gists{/gist_id}","starred_url":"https://api.github.com/users/MauroMombelli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MauroMombelli/subscriptions","organizations_url":"https://api.github.com/users/MauroMombelli/orgs","repos_url":"https://api.github.com/users/MauroMombelli/repos","events_url":"https://api.github.com/users/MauroMombelli/events{/privacy}","received_events_url":"https://api.github.com/users/MauroMombelli/received_events","type":"User","site_admin":false},"body":"While adding support to use real angle to servo i had to change a bit the servoMixer, so I decided to move a bit thing around, fix a couple of bug and optimize a bit\r\n\r\n* Fixed to use full range of the servo instead of limiting to [-500:500] (needed for 180° servo's)\r\n* Fixed to scale correctly the full range of the servo instead of cutting to [min:max] (needed if we want artificially limit servo PWM to smaller range than [-500:500], used when rate is < abs(100%) )\r\n* Support to use angle to PWM function, with INPUT_GIMBAL_PITCH and INPUT_GIMBAL_ROLL as an example. To prevent the scale of the range applied above to mess up with our fine-tuned PWM, just set needScale to false\r\n* calculate only used input and once for every rule: because if we use angle to PWM, the output PWM depend from the target servo. Anyway as no mixer reuse the same input two time, it is an optimization, and also make possible to (next point):\r\n* got rid of the \"input\" array\r\n* rate %, if used with fine mode, will act as a soft limitation over the full range instead of scaling the final output (of course scaling the output was making the precise angle to PWM fail)\r\n\r\nhope you will enjoy that code as much as i had to write it :)\r\n\r\npart of #925 ","created_at":"2015-12-17T17:30:43Z","updated_at":"2015-12-17T19:35:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d269b9f3af736ea1e87b6df07354f6d14147ee41","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591/commits","review_comments_url":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591/comments","review_comment_url":"https://api.github.com/repos/cleanflight/cleanflight/pulls/comments{/number}","comments_url":"https://api.github.com/repos/cleanflight/cleanflight/issues/1591/comments","statuses_url":"https://api.github.com/repos/cleanflight/cleanflight/statuses/13ce0cb213239d01732548243a555bbab5a0a439","head":{"label":"inkonova:servo-angle","ref":"servo-angle","sha":"13ce0cb213239d01732548243a555bbab5a0a439","user":{"login":"inkonova","id":12050328,"avatar_url":"https://avatars.githubusercontent.com/u/12050328?v=3","gravatar_id":"","url":"https://api.github.com/users/inkonova","html_url":"https://github.com/inkonova","followers_url":"https://api.github.com/users/inkonova/followers","following_url":"https://api.github.com/users/inkonova/following{/other_user}","gists_url":"https://api.github.com/users/inkonova/gists{/gist_id}","starred_url":"https://api.github.com/users/inkonova/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/inkonova/subscriptions","organizations_url":"https://api.github.com/users/inkonova/orgs","repos_url":"https://api.github.com/users/inkonova/repos","events_url":"https://api.github.com/users/inkonova/events{/privacy}","received_events_url":"https://api.github.com/users/inkonova/received_events","type":"Organization","site_admin":false},"repo":{"id":34325834,"name":"cleanflight","full_name":"inkonova/cleanflight","owner":{"login":"inkonova","id":12050328,"avatar_url":"https://avatars.githubusercontent.com/u/12050328?v=3","gravatar_id":"","url":"https://api.github.com/users/inkonova","html_url":"https://github.com/inkonova","followers_url":"https://api.github.com/users/inkonova/followers","following_url":"https://api.github.com/users/inkonova/following{/other_user}","gists_url":"https://api.github.com/users/inkonova/gists{/gist_id}","starred_url":"https://api.github.com/users/inkonova/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/inkonova/subscriptions","organizations_url":"https://api.github.com/users/inkonova/orgs","repos_url":"https://api.github.com/users/inkonova/repos","events_url":"https://api.github.com/users/inkonova/events{/privacy}","received_events_url":"https://api.github.com/users/inkonova/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/inkonova/cleanflight","description":"Clean-code version of the baseflight flight controller firmware","fork":true,"url":"https://api.github.com/repos/inkonova/cleanflight","forks_url":"https://api.github.com/repos/inkonova/cleanflight/forks","keys_url":"https://api.github.com/repos/inkonova/cleanflight/keys{/key_id}","collaborators_url":"https://api.github.com/repos/inkonova/cleanflight/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/inkonova/cleanflight/teams","hooks_url":"https://api.github.com/repos/inkonova/cleanflight/hooks","issue_events_url":"https://api.github.com/repos/inkonova/cleanflight/issues/events{/number}","events_url":"https://api.github.com/repos/inkonova/cleanflight/events","assignees_url":"https://api.github.com/repos/inkonova/cleanflight/assignees{/user}","branches_url":"https://api.github.com/repos/inkonova/cleanflight/branches{/branch}","tags_url":"https://api.github.com/repos/inkonova/cleanflight/tags","blobs_url":"https://api.github.com/repos/inkonova/cleanflight/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/inkonova/cleanflight/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/inkonova/cleanflight/git/refs{/sha}","trees_url":"https://api.github.com/repos/inkonova/cleanflight/git/trees{/sha}","statuses_url":"https://api.github.com/repos/inkonova/cleanflight/statuses/{sha}","languages_url":"https://api.github.com/repos/inkonova/cleanflight/languages","stargazers_url":"https://api.github.com/repos/inkonova/cleanflight/stargazers","contributors_url":"https://api.github.com/repos/inkonova/cleanflight/contributors","subscribers_url":"https://api.github.com/repos/inkonova/cleanflight/subscribers","subscription_url":"https://api.github.com/repos/inkonova/cleanflight/subscription","commits_url":"https://api.github.com/repos/inkonova/cleanflight/commits{/sha}","git_commits_url":"https://api.github.com/repos/inkonova/cleanflight/git/commits{/sha}","comments_url":"https://api.github.com/repos/inkonova/cleanflight/comments{/number}","issue_comment_url":"https://api.github.com/repos/inkonova/cleanflight/issues/comments{/number}","contents_url":"https://api.github.com/repos/inkonova/cleanflight/contents/{+path}","compare_url":"https://api.github.com/repos/inkonova/cleanflight/compare/{base}...{head}","merges_url":"https://api.github.com/repos/inkonova/cleanflight/merges","archive_url":"https://api.github.com/repos/inkonova/cleanflight/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/inkonova/cleanflight/downloads","issues_url":"https://api.github.com/repos/inkonova/cleanflight/issues{/number}","pulls_url":"https://api.github.com/repos/inkonova/cleanflight/pulls{/number}","milestones_url":"https://api.github.com/repos/inkonova/cleanflight/milestones{/number}","notifications_url":"https://api.github.com/repos/inkonova/cleanflight/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/inkonova/cleanflight/labels{/name}","releases_url":"https://api.github.com/repos/inkonova/cleanflight/releases{/id}","created_at":"2015-04-21T12:34:01Z","updated_at":"2015-12-16T16:47:04Z","pushed_at":"2015-12-17T17:05:07Z","git_url":"git://github.com/inkonova/cleanflight.git","ssh_url":"git@github.com:inkonova/cleanflight.git","clone_url":"https://github.com/inkonova/cleanflight.git","svn_url":"https://github.com/inkonova/cleanflight","homepage":"http://cleanflight.com","size":34733,"stargazers_count":2,"watchers_count":2,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":2,"default_branch":"master"}},"base":{"label":"cleanflight:master","ref":"master","sha":"56102f52c4152a69722b1e365ff0bfce4561dac5","user":{"login":"cleanflight","id":8776523,"avatar_url":"https://avatars.githubusercontent.com/u/8776523?v=3","gravatar_id":"","url":"https://api.github.com/users/cleanflight","html_url":"https://github.com/cleanflight","followers_url":"https://api.github.com/users/cleanflight/followers","following_url":"https://api.github.com/users/cleanflight/following{/other_user}","gists_url":"https://api.github.com/users/cleanflight/gists{/gist_id}","starred_url":"https://api.github.com/users/cleanflight/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cleanflight/subscriptions","organizations_url":"https://api.github.com/users/cleanflight/orgs","repos_url":"https://api.github.com/users/cleanflight/repos","events_url":"https://api.github.com/users/cleanflight/events{/privacy}","received_events_url":"https://api.github.com/users/cleanflight/received_events","type":"Organization","site_admin":false},"repo":{"id":9066347,"name":"cleanflight","full_name":"cleanflight/cleanflight","owner":{"login":"cleanflight","id":8776523,"avatar_url":"https://avatars.githubusercontent.com/u/8776523?v=3","gravatar_id":"","url":"https://api.github.com/users/cleanflight","html_url":"https://github.com/cleanflight","followers_url":"https://api.github.com/users/cleanflight/followers","following_url":"https://api.github.com/users/cleanflight/following{/other_user}","gists_url":"https://api.github.com/users/cleanflight/gists{/gist_id}","starred_url":"https://api.github.com/users/cleanflight/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cleanflight/subscriptions","organizations_url":"https://api.github.com/users/cleanflight/orgs","repos_url":"https://api.github.com/users/cleanflight/repos","events_url":"https://api.github.com/users/cleanflight/events{/privacy}","received_events_url":"https://api.github.com/users/cleanflight/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cleanflight/cleanflight","description":"Clean-code version of the baseflight flight controller firmware","fork":false,"url":"https://api.github.com/repos/cleanflight/cleanflight","forks_url":"https://api.github.com/repos/cleanflight/cleanflight/forks","keys_url":"https://api.github.com/repos/cleanflight/cleanflight/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cleanflight/cleanflight/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cleanflight/cleanflight/teams","hooks_url":"https://api.github.com/repos/cleanflight/cleanflight/hooks","issue_events_url":"https://api.github.com/repos/cleanflight/cleanflight/issues/events{/number}","events_url":"https://api.github.com/repos/cleanflight/cleanflight/events","assignees_url":"https://api.github.com/repos/cleanflight/cleanflight/assignees{/user}","branches_url":"https://api.github.com/repos/cleanflight/cleanflight/branches{/branch}","tags_url":"https://api.github.com/repos/cleanflight/cleanflight/tags","blobs_url":"https://api.github.com/repos/cleanflight/cleanflight/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cleanflight/cleanflight/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cleanflight/cleanflight/git/refs{/sha}","trees_url":"https://api.github.com/repos/cleanflight/cleanflight/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cleanflight/cleanflight/statuses/{sha}","languages_url":"https://api.github.com/repos/cleanflight/cleanflight/languages","stargazers_url":"https://api.github.com/repos/cleanflight/cleanflight/stargazers","contributors_url":"https://api.github.com/repos/cleanflight/cleanflight/contributors","subscribers_url":"https://api.github.com/repos/cleanflight/cleanflight/subscribers","subscription_url":"https://api.github.com/repos/cleanflight/cleanflight/subscription","commits_url":"https://api.github.com/repos/cleanflight/cleanflight/commits{/sha}","git_commits_url":"https://api.github.com/repos/cleanflight/cleanflight/git/commits{/sha}","comments_url":"https://api.github.com/repos/cleanflight/cleanflight/comments{/number}","issue_comment_url":"https://api.github.com/repos/cleanflight/cleanflight/issues/comments{/number}","contents_url":"https://api.github.com/repos/cleanflight/cleanflight/contents/{+path}","compare_url":"https://api.github.com/repos/cleanflight/cleanflight/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cleanflight/cleanflight/merges","archive_url":"https://api.github.com/repos/cleanflight/cleanflight/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cleanflight/cleanflight/downloads","issues_url":"https://api.github.com/repos/cleanflight/cleanflight/issues{/number}","pulls_url":"https://api.github.com/repos/cleanflight/cleanflight/pulls{/number}","milestones_url":"https://api.github.com/repos/cleanflight/cleanflight/milestones{/number}","notifications_url":"https://api.github.com/repos/cleanflight/cleanflight/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cleanflight/cleanflight/labels{/name}","releases_url":"https://api.github.com/repos/cleanflight/cleanflight/releases{/id}","created_at":"2013-03-27T23:49:19Z","updated_at":"2015-12-17T00:32:42Z","pushed_at":"2015-12-17T17:34:47Z","git_url":"git://github.com/cleanflight/cleanflight.git","ssh_url":"git@github.com:cleanflight/cleanflight.git","clone_url":"https://github.com/cleanflight/cleanflight.git","svn_url":"https://github.com/cleanflight/cleanflight","homepage":"http://cleanflight.com","size":87642,"stargazers_count":708,"watchers_count":708,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":723,"mirror_url":null,"open_issues_count":374,"forks":723,"open_issues":374,"watchers":708,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591"},"html":{"href":"https://github.com/cleanflight/cleanflight/pull/1591"},"issue":{"href":"https://api.github.com/repos/cleanflight/cleanflight/issues/1591"},"comments":{"href":"https://api.github.com/repos/cleanflight/cleanflight/issues/1591/comments"},"review_comments":{"href":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591/comments"},"review_comment":{"href":"https://api.github.com/repos/cleanflight/cleanflight/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/cleanflight/cleanflight/pulls/1591/commits"},"statuses":{"href":"https://api.github.com/repos/cleanflight/cleanflight/statuses/13ce0cb213239d01732548243a555bbab5a0a439"}}}}