找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 662|回复: 0
收起左侧

51单片机制作舵机的程序

[复制链接]
ID:420635 发表于 2025-9-6 16:52 来自触屏版 | 显示全部楼层 |阅读模式
  1. /******************************************/
  2. //
  3. // 主频12MHz  !!!!
  4. //
  5. /******************************************/
  6. #include <reg51.h>
  7. #include <intrins.h>
  8. // 定义常用数据类型别名
  9. #define uchar unsigned char
  10. #define uint  unsigned int
  11. #define u8 unsigned char
  12. #define u16  unsigned int
  13. #define int16 int
  14. // 定义PID参数
  15. #define KP 50  // 比例系数,可调整
  16. #define KI 10  // 积分系数,可调整
  17. #define KD 5   // 微分系数,可调整
  18. // 积分限幅
  19. #define INTEGRAL_LIMIT 1000
  20. //* STC15W408AS寄存器补充
  21. sfr AUXR1 = 0XA2;
  22. sfr AUXR = 0X8E;
  23. sfr TH2 = 0XD6;
  24. sfr TL2 = 0XD7;
  25. sfr P4 = 0xC0;
  26. sfr P5 = 0xC8;
  27. sfr SPSTAT = 0xCD;
  28. sfr SPCTL = 0xCE;
  29. sfr SPDAT = 0xCF;
  30. sfr P1M1 = 0x91;
  31. sfr P1M0 = 0x92;
  32. sfr P2M1 = 0x95;
  33. sfr P2M0 = 0x96;
  34. sfr P3M1 = 0xB1;
  35. sfr P3M0 = 0xB2;
  36. sfr P5M1 = 0xC9;
  37. sfr P5M0 = 0xCA;
  38. // 定时器2
  39. sfr T2H = 0xD6;
  40. sfr T2L = 0xD7;
  41. sfr IE2 = 0xAF;
  42. // ADC
  43. sfr ADC_CONTR = 0xBC;        // 带AD系列
  44. sfr ADC_RES = 0xBD;        // 带AD系列
  45. sfr ADC_RESL = 0xBE;        // 带AD系列
  46. sfr P1ASF = 0x9D;
  47. sfr PCON2 = 0x97;
  48. // ADC控制位定义
  49. #define ADC_POWER        0x80// ADC电源控制位
  50. #define ADC_FLAG        0x10// ADC完成标志
  51. #define ADC_START        0x08// ADC起始控制位
  52. #define ADC_SPEEDLL        0x00// 540个时钟转换一次
  53. #define ADC_SPEEDL        0x20// 360时钟
  54. #define ADC_SPEEDH        0x40// 180时钟
  55. #define ADC_SPEEDHH        0x60// 90时钟
  56. // 电机控制引脚
  57. sbit IA = P3^7;
  58. sbit IB = P3^6;
  59. // PPM信号相关变量
  60. u8 PPMCnt = 0;  // 收到PPM脉冲的次数
  61. bit PPMOK = 0;  // 是否收到一个PPM脉冲
  62. u16 time;       // PPM的高电平计数
  63. // ADC采样相关变量
  64. u16 ad[10];     // 存放10个ad数值的数组
  65. u16 ad1 = 0;    // 舵机的电位器的ad值
  66. u16 ad0 = 0;    // 舵机的电位器应该达到的AD值
  67. // 电机运行时间
  68. u8 t1;          // 电机运行持续毫秒数
  69. // PID相关变量
  70. int16 integral = 0; // 积分项
  71. int16 lastError = 0; // 上一次的误差
  72. int16 error;
  73. int16 derivative;
  74. /***************/
  75. /* 延时函数    */
  76. /***************/
  77. // 1ms延时函数
  78. void Delay1ms()                //@12.000MHz
  79. {
  80.      unsigned char i, j;
  81.      i = 12;
  82.      j = 169;
  83.      do
  84.      {
  85.          while (--j);
  86.      } while (--i);
  87. }
  88. // 毫秒级延时函数
  89. void delay_ms(unsigned char i)
  90. {
  91.      while (i--)
  92.      {
  93.          Delay1ms();
  94.      }
  95. }
  96. // 获取10位精度的ADC值
  97. u16 Get_ADC10bitResult(u8 channel)
  98. {
  99.      u16 adc;
  100.      if (channel > 7)        return 1024;        // 错误,返回1024, ADC通道号0~7,不能超过7        
  101.      ADC_RES = 0; // 高位ADC清零
  102.      ADC_RESL = 0; // 低位ADC清零
  103.      ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | channel; // 打开指定通道的ADC转换
  104.      _nop_();
  105.      _nop_();
  106.      _nop_();
  107.      _nop_(); // 对ADC_CONTR操作后要4T之后才能访问
  108.      while (!(ADC_CONTR & ADC_FLAG)); // 等待ADC转换完成
  109.      ADC_CONTR &= ~ADC_FLAG;        // ADC_FLAG手工清0,关闭 ADC
  110.      adc = ADC_RES;      // 得到高8位
  111.      adc <<= 2;
  112.      adc += ADC_RESL;     // 得到低2位
  113.      return adc;
  114. }
  115. // 定时器0初始化函数 当INT0也就是P3.2脚为高,定时器0才开始运行
  116. void Timer0Init(void)                //12.000MHz
  117. {
  118.      AUXR &= 0x7F;                // 定时器时钟12T模式
  119.      TMOD = 0x09;                // 16位不可重装模式        当INT0=1和TR0=1时,定时器0才能运行
  120.      TL0 = 0x00;                // 设置定时初值
  121.      TH0 = 0x00;                // 设置定时初值
  122.      TF0 = 0;                // 清除TF0标志
  123.      TR0 = 1;   // 定时器0开始计时
  124.      ET0 = 1;   // 允许ET0中断
  125.      EA = 1;           // 允许全部中断
  126. }
  127. // 定时器0中断函数(如果进入定时器0中断,说明无PPM脉冲)
  128. void T0_ISR(void) interrupt 1
  129. {
  130.      time = 1000; // 一旦出现溢出,说明无脉冲,让time默认=1000
  131.      PPMCnt = 0;  // 重新计数PPM
  132.      PPMOK = 0;  
  133. }
  134. // INT0初始化函数
  135. void INT0Init(void)
  136. {
  137.      IE0 = 0;        // 清除外中断0标志位
  138.      EX0 = 1;        // INT0 有效
  139.      IT0 = 1;        // INT0 下降沿中断
  140. }
  141. // INT0中断函数
  142. void INT0_ISR(void) interrupt 0
  143. {
  144.      u16 j;
  145.      TR0 = 0; // 当检测到INT0,也就是P3.2脚的高电平变成低电平的时候,停止定时器
  146.      j = ((u16)TH0 << 8) + TL0; // 计算定时器的时间
  147.      TH0 = 0; // 重新计时
  148.      TL0 = 0;
  149.      TR0 = 1; // 打开定时器
  150.      if (++PPMCnt >= 5) PPMCnt = 5; // 连续收到5次PPM,才认为PPM开始
  151.      // PPM的范围1000~2000,为了消除误差,所以用500~2500
  152.      if ((j >= 500) && (j <= 2500) && (PPMCnt == 5))
  153.      {
  154.          time = j;
  155.          PPMOK = 1;                // 标志收到一个脉冲
  156.      }
  157. }
  158. // 关掉电机
  159. void shut()
  160. {
  161.      IA = 0;
  162.      IB = 0;
  163. }
  164. // 电机正转
  165. void ab()
  166. {
  167.      IA = 1;
  168.      IB = 0;
  169. }
  170. // 电机反转
  171. void ba()
  172. {
  173.      IA = 0;
  174.      IB = 1;
  175. }
  176. // 冒泡法排序
  177. void mp_()   
  178. {
  179.      u8 i, j;
  180.      u16 m;
  181.      for (i = 1; i < 10; i++)
  182.      {
  183.          for (j = 0; j < 10 - i; j++)
  184.          {
  185.              if (ad[j] > ad[j + 1])
  186.              {
  187.                  m = ad[j];
  188.                  ad[j] = ad[j + 1];
  189.                  ad[j + 1] = m;
  190.              }
  191.          }
  192.      }
  193. }
  194. void main(void)
  195. {
  196.      u8 i = 0;
  197.      P3M0 = 0xC0; // 打开P3.6 P3.7的强推挽
  198.      shut();      // 先关掉电机
  199.      P1ASF = 0x02; // 打开P1.1的ADC转换功能
  200.      Timer0Init(); // 定时器0初始化
  201.      INT0Init();         // 中断0初始化
  202.      while (1)
  203.      {
  204.          if (PPMOK) // 当收到PPM信号才开始工作
  205.          {
  206.              ad0 = (time - 500) / 2; // PPM高电平时间简单的换算成AD值
  207.              for (i = 0; i < 10; i++)
  208.                  ad[i] = Get_ADC10bitResult(1); // 测量第1通道adc值,连续测量10次
  209.              mp_(); // 冒泡法对10个ad按大小进行排序
  210.              ad1 = 0; // 舵机电位器的ad值置初值
  211.              for (i = 1; i < 8; i++)
  212.                  ad1 += ad[i]; // 取中间8个ad值相加,去掉最大值和最小值。
  213.              ad1 = ad1 / 8; // 算平均AD
  214.              // 计算误差
  215.              error = ad0 - ad1;
  216.              // 计算积分项并限幅
  217.              integral += error;
  218.              if (integral > INTEGRAL_LIMIT)
  219.                  integral = INTEGRAL_LIMIT;
  220.              else if (integral < -INTEGRAL_LIMIT)
  221.                  integral = -INTEGRAL_LIMIT;
  222.              // 计算微分项
  223.              derivative = error - lastError;
  224.              // 保存本次误差用于下一次计算
  225.              lastError = error;
  226.              // 计算PID控制量
  227.              int16 pidOutput = (KP * error + KI * integral + KD * derivative) / 100;
  228.              // 控制量限幅
  229.              if (pidOutput > 255)
  230.                  pidOutput = 255;
  231.              else if (pidOutput < 0)
  232.                  pidOutput = 0;
  233.              t1 = (u8)pidOutput;
  234.              if (ad1 < ad0 - 5) // 前进
  235.              {
  236.                  ab();
  237.              }
  238.              else if (ad1 > ad0 + 5) // 后退
  239.              {
  240.                  ba();
  241.              }
  242.              delay_ms(t1);
  243.              shut(); // 这里关掉电机,有助于后面AD转换的数值稳定。
  244.              delay_ms(1); // 延时1ms,让舵机的电位器AD测量更准确一点。
  245.          }
  246.      }
  247. }
复制代码
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表