单片机s曲线计算_单片机 s曲线算法 - CSDN
  • 步进电机S曲线的生成

    2017-08-17 21:27:54
    S曲线相对于T型曲线S曲线的速度不会突变。S曲线的原始公式如下: y=K/(1+Exp(a-b*x))(K>0,b>0) Exp是指数函数,由这个公式可以看出y会随着x的增大,逼近K。把这个公式应用到电机控制之中,K看成最大的...

    S型曲线公式
    步进电机的速度从0变为一个比较大的速度,需要一个加速过程,否则会产生振动或是电机的堵转。电机加速通常有T型曲线和S型曲线两种方式。S型曲线相对于T型曲线,S型曲线的速度不会突变。S型曲线的原始公式如下:
    y=K/(1+Exp(a-b*x))(K>0,b>0)

    Exp是指数函数,由这个公式可以看出y会随着x的增大,逼近K。把这个公式应用到电机控制之中,K看成最大的速度Vmax,x看成时间t的变量,那么t=0时,就是电机的初始速度,即V0=Vmax/(1+Exp(a-b*(0)))=Vmax/(1+Exp(a));反推求出a与V0,Vmax之间的关系,a=In((Vmax-V0)/V0).b决定曲线的快慢,b越大V变化越快,也就越快接近Vmax。

    单片机一般采用定时器来产生脉冲控制电机的转动,产生脉冲的频率决定电机转动的快慢,这个频率与单片机的晶振的频率nXtal,定时器的预分频nPrevXtal,,定时器的自动重装载寄存器的数据Xload有关,晶振的频率由硬件决定,定时器的预分频在单片机的初始化的时候设置,那么我们要改变电机的运行速度,就只能通过改变定时器的的自动重装载寄存器的数据。通常的做法是把自动重装载寄存器的数据放到一个数组里面,现在要做的是如何生成这个数组的数据。我这里介绍使用上位机VC编写界面来生成这个数组及曲线。如下图:
    自动重装载寄存器数据

    S型变化曲线

    软件界面需要设置的数据有:晶振频率nXtal,预分频nPrevXtal,步进电机驱动器细分nPrevMotor,运行的最大速度nMaxSpeed,达到最大速度时的位置坐标(对应界面上的极限)nMaxPoint以及a,b参数是通过滑动条设置,a对应起始速度,b对应加速快慢.

    速度与自动重装载寄存器的关系
    电机的转速的单位一般是rpm,即一分钟多少转.如果电机的转速是V(rpm),那么自动重装载寄存器的数据应该是多少?步进电机驱动器细分为nPrevMotor,那么单片机发脉冲的速度是nPrevMotor*V/60每秒,即定时器一秒钟产生中断的个数。
    1/(Xload/(nXtal/nPrevXtal))=(nPrevMotor*V/60)
    Xload=60*nXtal/(nPrevXtal*nPrevMotor*V)
    其中V=Vmax/(1+Exp(a-b*t)),即
    Xload=60*nXtal*(1+Exp(a-b*t))/(nPrevXtal*nPrevMotor*Vmax)

    Xload是自动重装载寄存器的数据,nXtal是晶振频率,nPrevXtal是预分频系数。

    VC生成数组的代码如下:

    int nMaxPoint,nXtal,nPrevXtal,nPrevMotor,nMaxSpeed;
    nMaxPoint = GetDlgItemInt(IDC_EDIT_MAX_POINT);
    nXtal = GetDlgItemInt(IDC_EDIT_XTAL);
    nPrevXtal = GetDlgItemInt(IDC_EDIT_PREV_XTAL);
    nPrevMotor = GetDlgItemInt(IDC_EDIT_MOTOR_F);
    nMaxSpeed = GetDlgItemInt(IDC_EDIT_SPEED_MAX);
    double k;
    k= (double)nMaxSpeed;
    CString strArray;
    unsigned short nTimerCount;
    
    strArray.AppendFormat(_T("/*============================================================================\r\n"));
    strArray.AppendFormat(_T("晶振频率        = %d;\r\n"),nXtal);
    strArray.AppendFormat(_T("预分频频率 = %d;\r\n"),nPrevXtal);
    strArray.AppendFormat(_T("极限点       = %d;\r\n"),nMaxPoint);
    strArray.AppendFormat(_T("极限转速      = %d;\r\n"),nMaxSpeed);
    strArray.AppendFormat(_T("周脉冲数      = %d;\r\n"),nPrevMotor);
    strArray.AppendFormat(_T("S型曲线\r\n"));
    strArray.AppendFormat(_T("============================================================================*/\r\nunsigned int code WaitTable[]={\r\n"));
    
    for(int i= 0;i<nMaxPoint;i++)
    {
        nTimerCount = (nXtal)*(1+exp(a-b*i))/(nPrevXtal*nPrevMotor*nMaxSpeed);
    
        strArray.AppendFormat(_T("0x%04X, "),nTimerCount);
        if (((i+1)%10)==0)
        {
            strArray.AppendFormat(_T("  // %d\r\n"),(i/10+1)*10);
        }
    
    }
        strArray.AppendFormat(_T("};\r\n"));
        SetDlgItemText(IDC_EDIT_DISPLAY,strArray);

    就这样生成一个S曲线的数据数组,只需要把这个这个数组拷贝到你的单片机程序里面,定时器使用比较中断的方式,在中断处理函数里面,移动数组的下标即可。当然如果单片机是stm32,还可以使用DMA的方式来做,这样可以减少中断.
    S型曲线界面的绘制

    这个S曲线的显示使用的是一个Picture控件显示,根据输入晶振频率,预分频,最大速度,极限点。。来绘制,使用GDI相关接口在Picture控件窗口面绘制.代码如下:

    int CSCurveDlg::DrawCurve(void)
    {
        CWnd* pWnd = GetDlgItem(IDC_EDIT_DISPLAY);
        if(NULL!=pWnd)
        {
            RECT rect;
            pWnd->GetClientRect(&rect);
            int nHeight = rect.bottom-rect.top-4;
            int nWidth = rect.right-rect.left-4;
            CDC* pDC = pWnd->GetDC();
            pDC->FillSolidRect(&rect,RGB(255,255,255));
            POINT p1,p2;
    
            int i;
            double y1,y2;
            double k;
            k = (double)GetDlgItemInt(IDC_EDIT_SPEED_MAX);
            int nYmax=DrawTable(pDC,nWidth,nHeight);
            int nMaxPoint = GetDlgItemInt(IDC_EDIT_MAX_POINT);
            CSliderCtrl* pSlider1 = (CSliderCtrl*)GetDlgItem(IDC_SLIDER_ACC);
            b=(double)(((double)pSlider1->GetPos())/20);
    
            for (i=0;i<nMaxPoint;i++)
            {
                y1 = (nYmax-5)/(1+exp(a-(b*i)));
                y2 = (nYmax-5)/(1+(exp(a-b*(i+1))));
                p1.x=i*nWidth/nMaxPoint;
                p2.x=(i+1)*nWidth/nMaxPoint;
                p1.y = nHeight-y1;
                p2.y = nHeight-y2;
                pDC->MoveTo(p1);
                pDC->LineTo(p2);
            }
    
        }
        return 0;
    }

    源码下载

    展开全文
  • 步进电机 加减速 s曲线 c代码,之前做的项目,在stm32f103zet6实现的代码,可以自动根据位移,最大速度,最大加速度,跃度自动计算定时器的延时值
  • 程序功能:生成步进电机S曲线运动数组代码 程序语言:VB6.0 对应硬件线路连接见上章: https://blog.csdn.net/LuDanTongXue/article/details/87869557 对应单片机程序见上章(PC端程序显示正常,手机端程序后半...

    程序功能:生成步进电机S曲线运动数组代码
    程序语言:VB6.0

    对应硬件线路连接见上章:
    https://blog.csdn.net/LuDanTongXue/article/details/87869557
    对应单片机程序见上章(PC端程序显示正常,手机端程序后半段显示不出来):
    https://blog.csdn.net/LuDanTongXue/article/details/87869806
    程序详细使用方法请见:
    https://blog.csdn.net/LuDanTongXue/article/details/87886580
    程序下载地址:
    https://download.csdn.net/download/ludantongxue/11114203

    程序界面如下:
    步进电机S曲线+VB6
    S曲线算法参照该博文:
    https://blog.csdn.net/pengzhihui2012/article/details/52228822
    源码如下:

    Option Explicit
        Dim BuJuJiao As Double '步进电机步距角
        Dim F0 As Double '硬件频率
        Dim Nf As Double '定时器分频
        Dim v0 As Double '加速段启动速度
        Dim vt As Double '加速段最大速度
        Dim JSBuShu As Long '加速步数
        Dim JSJiaoDu As Double '加速段角度
        Dim XiFen As Double '驱动器细分
        Dim JSKuaiMan As Double '加速快慢,数值越大越慢
        Dim JSshijian As Double '加速时间
        Dim FenDuan As Double '将曲线分成梯形图
        Dim MCCiShu As Long '每个速断段脉冲次数
        Dim MCYuShu As Integer '分段不整除时最后的余数
        
        Dim Fmin As Double 'V0对应的脉冲频率
        Dim Fmax As Double 'Vt对应的脉冲频率
        Dim Fcurrent As Double '实时频率
        
        Dim DSChuZhi As Long '定时器1初值,采用溢出中断
        Dim i As Integer '计数
        Dim a0 As Long 'A0第一个脉冲的初值
        Dim DingShiQiFenPing As Integer '定时器1预分频数
        Dim ZhuanXiang As String '电机转动方向
        Public Function Max(a As Double, b As Double) As Double '求最大值
            If a < b Then
                Max = b
            ElseIf a >= b Then
                Max = a
            End If
        End Function
        Public Function ST(ax As Double, ay As Double, n As Double, m As Double) '图框初始化
        '图框初始化
            Dim Kuandanwei As Double
            Picture1.ScaleMode = 6 '设置图片框单位
            Kuandanwei = Picture1.Width / 400
            Picture1.ScaleTop = -Picture1.Height / 2 '定义坐标原点x
            Picture1.ScaleLeft = -Picture1.Width / 2 '-10 * Kuandanwei '定义坐标原点y
            Picture1.Circle (0, 0), 1000
        'X轴
            Picture1.Line (-8 * Kuandanwei, 0)-(380 * Kuandanwei, 0)
            Picture1.Line (380 * Kuandanwei, 0)-(370 * Kuandanwei, 10 * Kuandanwei)
            Picture1.Line (380 * Kuandanwei, 0)-(370 * Kuandanwei, -10 * Kuandanwei)
            Picture1.CurrentX = 380 * Kuandanwei
            Picture1.CurrentY = 0
            Picture1.Print "t"
        'Y轴
            Picture1.Line (0, 0.45 * Picture1.Height)-(0, -0.45 * Picture1.Height)
            Picture1.Line (0, -0.45 * Picture1.Height)-(-10 * Kuandanwei, -0.45 * Picture1.Height + 10 * Kuandanwei)
            Picture1.Line (0, -0.45 * Picture1.Height)-(10 * Kuandanwei, -0.45 * Picture1.Height + 10 * Kuandanwei)
            Picture1.CurrentX = 0
            Picture1.CurrentY = -0.5 * Picture1.Height
            Picture1.Print "S"
        '绘制坐标点
            Picture1.PSet (n * Kuandanwei, -ax * m / 3), vbRed
            Picture1.PSet (n * Kuandanwei, -ay * m / 3), vbBlue
            n = n + 1
            If n >= 360 Then
                n = n - 360
                Picture1.Cls
            End If
        End Function
        Public Function QuXian() '曲线图框初始化,BS步进电机步数为x轴,vt为Y轴
        '图框初始化
            
            Picture1.Cls
            Picture1.DrawWidth = 1
            Dim Bili As Double '绘图比例
            Bili = 0.7
            Dim Kuandanwei As Double 'x方向图片放缩比例
            Dim Gaodanwei As Double 'y方向图片放缩比例
            Form1.ScaleMode = 6 '设置FORM框单位
            Picture1.ScaleMode = 6 '设置图片框单位
            Kuandanwei = Picture1.Width / (JSBuShu) '根据步进电机总时间计算图片放缩比例
            Gaodanwei = Picture1.Height / Max(v0, vt)
            Picture1.ScaleTop = -Picture1.Height + 5 * Kuandanwei '定义坐标原点y
            Picture1.ScaleLeft = -5 * Kuandanwei '定义坐标原点x
            Picture1.CurrentX = 0
            Picture1.CurrentY = 0
            Picture1.Print "0"
        'X轴
            Dim x1 As Double
            Dim y1 As Double
            Dim x2 As Double
            Dim y2 As Double
            Dim x3 As Double
            Dim y3 As Double
            x1 = Bili * (JSBuShu * Kuandanwei)
            y1 = Bili * 0
            x2 = Bili * (x1 - 3) ' 5 * Kuandanwei)
            y2 = Bili * (y1 + 3) '5 * Kuandanwei)
            x3 = x2
            y3 = -y2
            Picture1.Line (0, 0)-(x1, y1)
            Picture1.Line (x1, y1)-(x2, y2)
            Picture1.Line (x1, y1)-(x3, y3)
            Picture1.CurrentX = x1
            Picture1.CurrentY = y1
            Picture1.Print Int(JSshijian * 1000) & "ms"
        'Y轴
            Dim x4 As Double
            Dim y4 As Double
            Dim x5 As Double
            Dim y5 As Double
            Dim x6 As Double
            Dim y6 As Double
            x4 = Bili * 0
            y4 = Bili * (-Max(v0, vt) * Gaodanwei)
            x5 = Bili * (x4 - 3) '-5* Kuandanwei)
            y5 = Bili * (y4 - 3) '-5* Kuandanwei)
            x6 = -x5
            y6 = y5
            Picture1.Line (0, 0)-(x4, y4)
            Picture1.Line (x4, y4)-(x5, y5)
            Picture1.Line (x4, y4)-(x6, y6)
            Picture1.CurrentX = x4 + Kuandanwei
            Picture1.CurrentY = y4
            Picture1.Print Max(v0, vt) & "r/s"
            
        '绘制坐标点
            Picture1.DrawWidth = 5
            Dim i As Integer
            For i = 1 To JSBuShu
                Picture1.PSet (Bili * i * Kuandanwei, Bili * -Gaodanwei * (v0 + (vt - v0) / (1 + Exp(-JSKuaiMan * (i - JSBuShu / 2) / (JSBuShu / 2))))), vbRed
            Next
        End Function
        Private Sub Command1_Click()
        BuJuJiao = Text2.Text
        F0 = Text3.Text
        Nf = Text4.Text
        v0 = Text5.Text
        vt = Text6.Text
        JSJiaoDu = Text7.Text
        XiFen = Text8.Text
        JSKuaiMan = Text9.Text
        If Text4.Text = 1 Then
        DingShiQiFenPing = 1
        ElseIf Text4.Text = 8 Then
        DingShiQiFenPing = 2
        ElseIf Text4.Text = 64 Then
        DingShiQiFenPing = 3
        ElseIf Text4.Text = 256 Then
        DingShiQiFenPing = 4
        ElseIf Text4.Text = 1024 Then
        DingShiQiFenPing = 5
        End If
        
        JSBuShu = JSJiaoDu / (BuJuJiao / XiFen)
        Fmin = v0 * 360 / (BuJuJiao / XiFen)
        Fmax = vt * 360 / (BuJuJiao / XiFen)
        
        Text1 = ""
        JSshijian = 0
        
        ''''''''''''''''''''''''''''加速段A0数组生成'''''''''''
            For i = 1 To JSBuShu
                Fcurrent = Fmin + (Fmax - Fmin) / (1 + Exp(-JSKuaiMan * (i - JSBuShu / 2) / (JSBuShu / 2)))
                DSChuZhi = 65535 - 1 / (Fcurrent * 2) * F0 / Nf '16位定时器
                If i <> JSBuShu Then
                    Text1 = Text1 & "0X" & Hex(DSChuZhi) & ","
                Else
                    Text1 = Text1 & "0X" & Hex(DSChuZhi)
                End If
                JSshijian = JSshijian + 1 / Fcurrent
            Next
        
        a0 = 65535 - 1 / ((Fmin + (Fmax - Fmin) / (1 + Exp(-JSKuaiMan * (1 - JSBuShu / 2) / (JSBuShu / 2)))) * 2) * F0 / Nf
        Text10 = JSBuShu
        Text12 = JSshijian * 1000
        Text1 = "A[" & JSBuShu & "]={" & Text1 & "};" & vbCrLf & vbCrLf
        
        ''''''''''''''''''''''''''''
        Text11.Text = (2 * F0 * BuJuJiao) / (65535 * Nf * 360 * XiFen) '计算最小启动速度
        
        QuXian '绘制速度曲线
        End Sub
    

    微信ID:saskingku

    展开全文
  •  首先在本设计中采用的步进电机控制方案为,单片机+16位定时器比较匹配中断(最好是16位及其以上)+步进电机驱动+42步进电机。较高的定时器精度能够实现更好的控制。  在步进电机控制中往...

        首先感谢以下博客的博主提供的参考公式:https://blog.csdn.net/pengzhihui2012/article/details/52228822?locationNum=6

        首先在本设计中采用的步进电机控制方案为,单片机+16位定时器比较匹配中断(最好是16位及其以上)+步进电机驱动+42步进电机。较高的定时器精度能够实现更好的控制。

        在步进电机控制中往往需要对步进电机进行加减速控制,以达到平缓启停或者达到较高转速而不失步停转的目的,而在加减速控制中控制方法有两类:

        1.查表法;

        查表法简单来说就是通过曲线公式预先计算出加速过程的各个点,再将该点转化为定时器的比较匹配值,载入数组中,查询数组值即可达到加减速的目的。优点是运算速度快,占用较少的CPU资源,缺点也很明显。    1.占用较大的存储空间,一般加速的点数都在300-2000点(细分更高的画可能会更高),若想获得更平滑的效果,点数甚至更高,这将会占用大量的单片机内存或者程序存储空间,如果系统支持一般推荐将数组保存在单片机的程序存储空间,以节省宝贵的Ram资源,例如在Arduino uno 中,若直接采样2000点放到数组里内存直接爆满(328的运行内存2K....)!,好在他提供了 PROGMEM 的操作方式,可以将数组保存到程序存储空间。再用 OCR1A =  pgm_read_word_near(&AccStep[acc_count]);将数组读出。具体实现方法文后有详细说明。2.更改速度、加速度等不方便,每次更改速度都需要重新生成一次表格,加速度的值更是难以设置,对于我目前的水平是这样的,应该是可以通过算法增大或者缩放加减速表格的,貌似开源3D打印固件Marlin中是这样的。

        2.实时生成法;

        实时生成法,可能会要求更高的CPU计算能力,比较出名的算法是AVR446:Linear speed control of stepper motor里面提供了详细的计算以及详细的实现方法,加速过程中实时计算下一个比较匹配值,以实现加减速的实时控制,优点挺多,控制加减速度,速度等参数更加方便,因为可以通过设定参数实时计算出来,缺点就是比较考验单片机的运算能力,但在AVR446提到的算法中也能在运算能力较低的单片机中实现。具体AVR446的实现将在另一个文章中说明。

        加速过程实现方法曲线一般有梯型曲线法以及S(Sigmoid)曲线法,其他接触过的还有修正正弦曲线法(用在机械臂的轨迹规划中),梯形曲线法一般通过加速度公式(S = a*t*t/2)直接求解,S曲线法则是通过SigMoid函数变形后求解。

        本文章主要介绍SigMoid函数用以步进电机的控制方法。

        1.基础知识:

        步进电机速度计算,做过步进电机控制都知道步进电机的速度跟脉冲频率是直接挂钩的,单片机每发出一个脉冲,步进电机运行一步(转过一个步距角),步距角与步进驱动细分挂钩,例如常用42步进电机步距角参数是1.8°/step,假设通过步进驱动细分后,细分为2,则电机实际每脉冲将运行1.8/2 = 0.9 °。单片机输出脉冲一般通过比较匹配中断的方式使脉冲引脚发出脉冲,则可以计算出单片机发出脉冲的时间间隔为(运行一步的时间)  = 比较匹配值 * (1/ 定时器计数频率 ),那这样我们知道了路程(步距角),时间(定时器频率及比较匹配值),就可以计算速度了,但是我们需要将角度换算一下采用弧度制(我在设计的时候采用了 弧度制,且AVR446中采用的也是弧度制,这里是为了统一),rad =  π/180×角度,这里我们就能算出1rad ≈ 57.3°,那我们的 步距角 = (π / 180) x (1.8/div) div是步进细分数。设角速度为1rad/s 则  他等于 57.3°/s = (57.3/360)*60/min = 9.55r/min。具有以上知识后就可以将转速(r/min)转换到定时器的比较匹配值了,例如:在我的设计中单片机定时器的计数频率为250Khz、我希望电机运行在300r/min,步进驱动不细分,则有(1.8x(π/180))/(OCR/250000)=300/9.55  所以OCR =   (Fcnt * Math.PI) / (100 * Div * (StepSpeed / 9.55))其中Fcnt是定时器计数频率,Div是驱动细分,StepSpeed是转速。,其中,OCR下的实际电机频率为 Fcnt/OCR ,请自行推导一下,强化记忆。

        2.SigMoid 曲线

        曲线的原型是:

        其值域是0~1,因此需要进行变形以便于使用,具体变形请参考文章开头的博客,这里不再赘述,仅做简单描述,可能形式略有不同但它们都是一样的。

        关于中心点(a,b)对称的变形公式:

        y = 2b / 1+E^(4k(a-x))

        

     

    参数说明
    中心点 (a,b)
    过中心点时变化速率 k
    y最大值是2b  b
    改变a可以改变中心点的位置 a

        其中需要注意的是,一般步进电机启动不会从0开始,而从某个频率开始启动,所以Y需要加上一个启动频率,正如文章前面博客中的公式一样,请各位自行理解体会里边的变形方法。

        本设计采用VB.NET(.NEF FRAMEWORK 4.6.2)实现上位机生成一个加速数组,数组值就为比较匹配值,直接复制就能使用,界面设计如下:

        设定好各个参数,点击生成,就能在右边的文本框中显示一个生成的数组,(生成速度受点数影响,技术太渣 还不会解决这个文本框显示过慢的问题),点击导出就能将文本框内容导出为.txt文件,方便使用。

        示例,假设步进电机步距角为1.8°,步进驱动的细分为8,启动频率500Hz,计数器频率为250Khz,期望速度为500r/min,加速步数为1000.则有以下运行结果;

    VB.NET核心代码实现如下:

        Dim img As New Bitmap(500, 500)
        Private Sub GeneratAcc_Click(sender As Object, e As EventArgs) Handles GeneratAcc.Click
            Dim G = Graphics.FromImage(img)
            'Dim G As Graphics = PictureBox1.CreateGraphics '定义picturebox 绘图
            Dim Gpen As New Pen(Color.Black, 1)     '定义笔参数
            Dim cnt As Integer = 0                  '循环数
            Dim OldX As Int32 = 0                   '保存前一次绘图坐标
            Dim OldY As Int32 = 500
            Dim tmpx As Single                      'X轴缩放因子
            Dim tmpy As Single                      'Y轴缩放因子
    
            dataTextBox1.Text = ""              '清零数组显示
    
            Fcnt = Val(TimFreBox.Text) * 1000   '获取定时器计数频率 单位Hz
            MinFre = Val(StartFreBox.Text)      '最小启动频率
            Steps = Val(AccStepBox.Text)        '加减速步数
            Div = Val(DivBox.Text)              '获取驱动细分
            StepSpeed = Val(MaxSpeedBox.Text)   '获取最大速度
            Fle = Val(FleBox.Text)              '设置加速区间大小
            num = Steps / Val(NumBox.Text)      '设置曲线对称系数
    
            MaxFre = (Fcnt * Math.PI) / (100 * Div * (StepSpeed / 9.55)) '求出最大速度时匹配寄存器的值
            'MaxFre = (1.8 * Math.PI * Fcnt * StepSpeed) / (180 * 9.55 * Div)
    
            MaxFre = Fcnt / MaxFre              '求出设定的最大速度匹配值下的频率
            SpeedFre.Text = MaxFre & " Hz"
    
            tmpy = 500 / MaxFre '求出缩放因子
            tmpx = 500 / Steps
    
            Dim mydata(Steps) As String
            '求解并绘出曲线
            For cnt = 0 To Steps
    
                Fre = MinFre + ((MaxFre - MinFre) / (1 + Math.E ^ (-Fle * (cnt - num) / num))) '计算曲线频率数据
                mydata(cnt) = Math.Round(Fcnt / Fre) 'Convert.ToInt32(Fcnt / Fre)            '转化为OCR匹配值
    
                G.DrawLine(Gpen, OldX, OldY, cnt * tmpx, 500 - (Fre * tmpy)) '画线
                OldX = cnt * tmpx           '保存前一次绘图坐标
                OldY = 500 - (Fre * tmpy)
    
                PictureBox1.Image = img
            Next
            '保存数组格式
            dataTextBox1.Text = "#define ACC_STEP_NUM " & Steps & vbCrLf & "unsigned short AccStep[ACC_STEP_NUM] = {"
            'dataTextBox1.Text = 
            '保存数组格式
    
            ProgressBar1.Maximum = Steps
            ProgressBar1.Visible = True
            For cnt = 0 To Steps
                If (cnt Mod 10) = 0 Then            '每10个数据一行
                    dataTextBox1.Text += vbCrLf
                End If
    
                If cnt = Steps Then
                    'dataTextBox1.Text += mydata(cnt)
                    dataTextBox1.Text += "};"
                Else
                    dataTextBox1.Text += mydata(cnt) & ","
                End If
                ProgressBar1.Value = cnt
            Next
            ProgressBar1.Visible = False
        End Sub

        在Arduino Uno(ATmega328)上的运行示例:

        如同前面提到的,大数组不应该直接放到内存,而是放在程序存储空间中,以免Ram不足,在设计中采用16位定时器1计数频率为250Khz,CTC模式,加减速曲线对称,数组太长不贴。

    #define ARR_MAX 3000  
    const  unsigned short AccStep[ARR_MAX] PROGMEM  = {*******} ;//生成的加减速数组
    
    #define ACCEL   1	//电机运行状态标志位
    #define DECEL   2
    #define RUN     3
    #define STOP   0
    long step_count;	//步数计数
    int acc_count;		//加速计数
    int dcc_count;		//减速计数
    uint8_t flag = STOP;//开始时的状态
    
    void SetSteps(long steps); 			   //设定电机步数及方向 正值代表正传 负值代表反转
    void StepRun(void);					   //启动电机
    
    void SetSteps(long steps)
    {
    	if(steps<0)
    	{
    		digitalWrite(2,0);      //设定电机运行方向
    		step_count = -steps;    //赋值步数
    	}
    	else
    	{
    		digitalWrite(2,1);
    		step_count = steps;
    	}
    }
    
    void StepRun(void)
    {
    	TCCR1B = (1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);  //16M / 250kHZ
    	OCR1A = 15; //随便开始一次中断	
    	flag = ACCEL; //进入加速状态
    }
    /*定时器1 初始化*/
    void Timer1Init(void)
    {
    	TCCR1A = 0;
    	TCCR1B &= ~((1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10));
    	TIMSK1 = (1<<OCIE1A);
    	OCR1A = 15;
    	sei();
    }
    /*初始化*/
    void setup() {
      // put your setup code here, to run once:
    pinMode(2,OUTPUT);
    pinMode(3,OUTPUT);
    
    Timer1Init();
    }
    /*主循环*/
    void loop() {
      // put your main code here, to run repeatedly:
      
      SetSteps(-16000);
      StepRun();
      delay(5000);
    }
    /*OCR1A 比较匹配中断 CTC 模式*/
    ISR(TIMER1_COMPA_vect)
    {
    	switch(flag)  //查询状态
    	{
    		case STOP  :{
    			TCCR1B &= ~((1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10));
    			acc_count =0;
    			step_count = 0;
    			dcc_count = 0;
    		}break;
    		
    		case ACCEL :{
    			acc_count++;
    			step_count--;
    			
    			digitalWrite(3,1); //输出一个脉冲
    	                digitalWrite(3,0);
    			
    			OCR1A =  pgm_read_word_near(&AccStep[acc_count]);//查表
    			if(acc_count == ARR_MAX-1) flag = RUN;
    		}break;
    
    		case RUN   :{
    			step_count--;
    			
    			digitalWrite(3,1);
    	                digitalWrite(3,0);
    			
    			if(step_count == ARR_MAX-1) 
    			{
    				flag = DECEL;
    				dcc_count = ARR_MAX-1;
    			}	
    		}break;
    		
    		case DECEL :{
    			dcc_count--;
    			step_count--;
    			
    			digitalWrite(3,1);
    	                digitalWrite(3,0);
    			
    			OCR1A =  pgm_read_word_near(&AccStep[dcc_count]);
    			if(acc_count == 0 || step_count==0) flag = STOP;
    		}break;
    	}
    }

    编译结果:

    可以看到,数组已经不占用可怜的Ram了。

    这个算法生产的加减速表用起来还可以,就是中间加速过程有点太猛了,有点像直接甩上去一样,可能是我设置的速度太高了,加速步数太短,在低速时,运行状态还算不错。

    学无止境,到最后怎么运用到各个项目中,还的需要大量的实践。

     对步进电机控制,机械臂正逆解感兴趣的朋友可以留言一起交流,搞点事情什么的大笑。   

    每错!这是我第一篇博客,记录一下学到的一些知识。

     

    2018.7.28 更新:需要注意的是,这里仅仅计算出了每个离散加速点的频率,实际使用中不可能每个频率点只发出一个脉冲,否则就像没有加速一样,解决的办法就是根据实际情况来调整每个频点发送的脉冲数,低频到高频脉冲数最好相应的也由少变多,这样可以改善加速效果。感谢各位评论让我发现不足的地方!

    2018.8.06 更新: 看到回复中许多人对数组的使用方式存在疑问,并且之前我个人也存在误区认为每个频段只发 一个脉冲,实际上细想下来并不科学,例如我细分很大,5000,1.8度步距角,那么我转1.8度需要5000步,我加速点2000步 那加速 路程极短, 加速时间极短,那显然不符合逻辑, 查阅 一些资料发现,在加速时应该在每一个频段停留一段时间,或者说,每个频段多发一些脉冲,低频时脉冲少,高频时脉冲多,这里给大家提供几个思路,用来解决每个频段应该发多少脉冲的问题。首先我们根据以下数据生成曲线:步距角1.8°,细分8,定时器计数频率250K,启动频率600HZ ,最大转速500r/min,加速步数为600步。

    第一种方法:将600个频段划分为10个部分,这10个部分的频段脉冲数分别为{5,5,10,10,20,20,30,30,40,40},这样每读取 一个频段就按该频段的步数发脉冲,当然这是一个想法、思路。需要注意的是,如果启动频率过低,或者低频时发出的脉冲太多将会影响加速时间,加速效果不好,所以应当合理选择启动频率和合理设计低频时的脉冲数。

    第二种方法:这种方法是一种实时计算的方法,我目前正在测试,效果感觉良好,思路很简单,我添加一个加速时间的条件,然后将时间按自己需求离散为这个频段时间间隔,根据这个时间间隔以及频率可以轻易计算出这个频段的步数,比如我目前加速步数是600步,那么将加速时间设置为600ms,显然为了满足600ms的加速时间我可以简单暴力的将每个频段的持续时间划分为1ms,由已知条件计数频率以及当前频段的比较匹配值即可计算,按照以下公式即可,注意时间单位的换算。

                                

    那么根据我的已知条件,我的每个频段的脉冲为 = 250.0f / (float)OCR。具体看以下加速度段程序实现。

    					case ACCEL :{
    			                    if(cstep==0)
    			                    {	
    				                    cccv = 250.0f / (float)AccStep[acc_count];
    				                    cstep = (cccv <= 1) ? 1 : ceil(cccv); 
    				                    OCR1A = AccStep[acc_count];
    				                    acc_count++;
    				                    if(acc_count==ARR_MAX-1) flag = RUN;
    			                    }
    			                    else
    			                    {
    				                    digitalWrite(3,1);
    				                    step_count--;
    				                    cstep--;
    				                    digitalWrite(3,0);			
    			                    }
    		                    }break;

    舍入函数看个人需求,ceil 或者floor 或者四舍五入,此算法解决频段脉冲数问题,并且需要给定加速时间,限制加速时间,算法还有优化空间,欢迎讨论,具体思路是高频段时间停留更久(目前是每个频段停留1ms,当然计算是有误差的,毕竟只能算出整数,控制加速时间在设定值附近吧),另外一个主意的问题是,在数组生成的最后几组中可能最高转速也包含在里边,根据需要可以适当修改数组,减少不必要的加速段。

     20190125更新:实际上在中断里引入浮点运算是不可取的,在几个月前我已经找到了更好的计算每个频段应该发多少个脉冲的算法,只包含一个加法和比较,有时间再更新上来吧。

    软件:https://download.csdn.net/download/renjiankun/10452200 

    测试视频:https://v.youku.com/v_show/id_XMzc2NzkyODg0OA==.html?spm=a2h3j.8428770.3416059.1

    展开全文
  • 作业需要,需要在单片机上显示距离与ad值大小的关系,理论推倒得到这两个的关系是抛物线,因此查书写了这段c语言程序。 具体的证明过程我也不会,书上提到了对于抛物线采用多项式拟合的方法,好像是将x,x^2,看成了...

    作业需要,需要在单片机上显示距离与ad值大小的关系,理论推倒得到这两个的关系是抛物线,因此查书写了这段c语言程序。
    具体的证明过程我也不会,书上提到了对于抛物线采用多项式拟合的方法,好像是将x,x^2,看成了两个变量。
    但核心公式只有一个,下面这个正则方程组
    在这里插入图片描述
    把上面这个方程简写成 xa=y(x、a、y分别对应一个矩阵)可以得到a=x^(-1)y
    其中x^(-1)表示x的逆矩阵
    具体的算法步骤是
    1.求x的伴随矩阵
    2.对矩阵x进行行列式操作
    3.x的逆矩阵=x的伴随矩阵/x的行列式的值
    4计算x^(-1)y
    在下面的代码中对求伴随矩阵进行了简化,减少了一点运算量。

    #include "stdio.h"
    #ifndef uint8
     #define uint8 unsigned short int
    #endif
    char Least_Squarel_Linear_Fit_y_2a0_4a1x_4a2xx(float *x,float *y,uint8 n,float *a0,float *a1,float *a2)
    {
      float temp=0;
      float x0=n,x1=0,x2=0,x3=0,x4=0,x0y1=0,x1y1=0,x2y1=0;//表示各项的求和
      float x0x2,x0x3,x0x4,x1x1,x1x2,x1x3,x1x4,x2x2,x2x3,x2x4,x3x3;
      float a_banshui[3][3],a_ni[3][3];//这个是伴随矩阵和逆矩阵
      float a_abs;//矩阵a的行列式
      for(uint8 i=0;i<n;i++)
      {
        x0y1+=*(y+i);
        temp=*(x+i);
        x1+=temp;
        x1y1+=temp*(*(y+i));
        temp*=*(x+i);
        x2+=temp;
        x2y1+=temp*(*(y+i));
        temp*=*(x+i);
        x3+=temp;
        temp*=*(x+i);
        x4+=temp;
        }
      //后面要用到的数据,先算出来
      x0x2=x0*x2;
      x0x3=x0*x3;
      x0x4=x0*x4;
      x1x1=x1*x1;
      x1x2=x1*x2;
      x1x3=x1*x3;
      x1x4=x1*x4;
      x2x2=x2*x2;
      x2x3=x2*x3;
      x2x4=x2*x4;
      x3x3=x3*x3;
      //计算伴随矩阵,行列式,求逆矩阵,其实可以利用对称性再减少运算
      a_banshui[0][0]= (x2x4-x3x3);  a_banshui[0][1]=-(x1x4-x2x3);  a_banshui[0][2]= (x1x3-x2x2);
      a_banshui[1][0]=-(x1x4-x2x3);  a_banshui[1][1]= (x0x4-x2x2);  a_banshui[1][2]=-(x0x3-x1x2);
      a_banshui[2][0]= (x1x3-x2x2);  a_banshui[2][1]=-(x0x3-x1x2);  a_banshui[2][2]= (x0x2-x1x1);
      //计算矩阵对应行列式的值
      a_abs=(x0*a_banshui[0][0]+x1*a_banshui[0][1]+x2*a_banshui[0][2]);  
      //计算逆矩阵
      for(uint8 i=0;i<3;i++)
      {
        for(uint8 j=0;j<3;j++)
        {
            a_ni[i][j]=a_banshui[i][j]/a_abs;
        }
      }
      *a0=a_ni[0][0]*x0y1+a_ni[0][1]*x1y1+a_ni[0][2]*x2y1;
      *a1=a_ni[1][0]*x0y1+a_ni[1][1]*x1y1+a_ni[1][2]*x2y1;
      *a2=a_ni[2][0]*x0y1+a_ni[2][1]*x1y1+a_ni[2][2]*x2y1;
      return 1;
    }
    
    #define N 10
    float x[N]={1,2,3,4,6,7,8,9,11,12};
    float y[N]={2,3,6,7,5,3,2,1,1,1};
    float a0,a1,a2;
    int main()
    {
     Least_Squarel_Linear_Fit_y_2a0_4a1x_4a2xx(x,y,N,&a0,&a1,&a2);
     printf("a0=%f\na1=%f\na2=%f\n",a0,a1,a2);
     while(1);
    }
    

    结果与excel比较
    在这里插入图片描述

    展开全文
  • 简单说明一下硬件资源,需要用到STM32两个定时器,TIM1产生PWM脉冲并对脉冲个数计数,TIM2开启定时中断用于算法的实现。采用CubeMX+Hal库配置,这里不做详细介绍,重点介绍S型...首先了解一下S曲线函数,见下图: ...

    简单说明一下硬件资源,需要用到STM32两个定时器,TIM1产生PWM脉冲并对脉冲个数计数,TIM2开启定时中断用于算法的实现。采用CubeMX+Hal库配置,这里不做详细介绍,重点介绍S型加减速算法的实现。

    首先了解一下S曲线函数,f(x)=1/(1+e^(-x)),这是S曲线最原始的函数,为什么叫“最原始的函数”,因为需要对其进行变换才能为我们所用,后面会详细介绍。取x∈[-10,+10]之间的所有整数在excel(是个好东西)里绘制S曲线图像,可以看到在x=-7和x=+7时f(x)分别接近最小值0和最大值1,所以我们就假定x=-7时f(x)min=0,x=+7时f(x)max=1 (使用过程中x=±7时已经满足我的精度要求,如果你需要更高的精度x可以取±8、±9...)。

    图1:原始函数图像

    在实际的过程中我们更希望x=0时f(x)=0,这样我们就能更好的把控程序,该怎么办呢?函数变换。我们高中时都学过把函数图像沿x轴平移、沿y轴平移、函数拉伸等等(在这里要感谢一位执教于高中数学的高中同学妹纸),上面讲到原始函数图像在x=-7时f(x)=0,如果把曲线图像向沿x轴向右平移7个单位,f(x)在x=0时不就等于0了?曲线图像向右平移几个单位那么x减几,向左平移几个单位那么x就加几,我们需要向右平移7个单位那么-(x-7)即可,得到一个新的函数f1(x):

    图2:第一次变换

    可以发现经过右移7个单位后得到的函数,当x=0时f1(x)的值等于原始函数在x=-7时的值,x=14时f(x)的值等于原始函数在x=7时的值,这样我们就把原始函数f(x)中x∈[-7,+7]的f(x)值域转移到经过第一次变换后得到的1f(x)  x∈[0,+14]的值域了,是不是离目标更近一步了?别急。

    经过第一次变换后得到的f1(x)在x∈[0,+14]时,x取该区间里的15个整数f1(x)即可由最小值0按照S型轨迹增加到最大值1,当然这期间x也可以取值为0.1、0.2、0.3......13.7、13.8、13.9等等这样的小数。但是我们程序里的设计思路是按次加速,加速n次后频率要达到最大。“次”是整数,我们也不可能说加速1.1次、加速1.2次,当x∈[0,+14]时,经过14次加速即可加速至至最大,显然加速过程不够平滑。我们希望x在某个区间里可以取值100个、200个、任意个整数,f(x)都能够按照S型轨迹变化,这样加速次数就可以灵活控制,这时我们需要对f(x)做横向拉伸变换。

    我们先看一下效果:

    图3:  -0.5x+7

    图3是x的系数除以2,得到的函数记为f2(x)。

    图4:-2x+7

    图4是x的系数乘以2,得到的函数记为f3(x)。

    可以看到,f2(x)和f3(x)达到相同的最小值最大值时能够取得的x最大整数值分别为f1(x)的2倍、1/2倍。f1(x)在x=14时取得最大值、f2(x)在x=28时取得最大值、f3(x)在x=7时取得最大值,且最大值均相等。记f(x)=1/(1+e^(-ax+b)),那么我们是否可以认为当f(x)的值由0按照S型轨迹增至1时,能够取得x的整数个数为14*(1/a)个?OK,我们再来验证一下,取a=0.1,b=7,得到的函数记为f4(x):

    图5:0.1x+7

    果然,x能够取得最大的整数值为140,为f1(x)的x=14的10倍,也就是说我们可以加速140次至最大值,假设成立!

    看到这儿是不是觉得胜利就在眼前了?最后我们再来优化一下,通过上诉变换我们可以得到14的整数倍数的最大加速次数,如果我们能得到1或者10的整数倍数的最大加速次数不是更好?很简单只需把前面讲到的-ax+b改为-(14/n)x+b,其中14/n = a,n为加速次数,例如我们想通过100次加速,那么函数即为f(x)=1/(1+e^((14x/100)+7)),我们前面首次变换时将函数图像沿x轴右移了7个单位( x∈[-7,+7] ),而14=7*2,数学是不是很美妙?有空可以验证一下如果向右平移8个单位、9个单位...,是不是可以将x的系数该为16/n、18/n?

    在驱动步进电机过程中我们希望PWM脉冲频率按照S型轨迹变化,也就说f(x)的值对应PWM脉冲频率Freq。上诉函数中f(x)大小都在1以下,我们需要PWM脉冲频率达到几十k或者几百k。这这个简单呀,乘以倍数Freq=n*f(x),最大20kHz,就乘以20000、最大100kHz就乘以100000。

    我们来看一个很漂亮的S曲线:

    图6:理想的S曲线

    按照上面讲的变换规则,最大PWM脉冲频率50kHz、经过500次加速至最大频率,生成的S曲线。

    我们讲完了所有的S曲线变换规则,那么到底如何通过程序实现PWM脉冲频率按照S型曲线变换呢?

    最初想通过公式计算得到本次的PWM脉冲频率值,算一次数据更新一下自动重载值,但是涉及到浮点数运算,对于STM32F103这样不带FPU的单片机来说计算一次数据需要100us+的时间,比较耗时。那么我们能不能提前算好数据,在调节频率时让单片机自己选择一个数据来更新重载值呢?好主意!

    void CurveS_init(uint32_t *pbuff,uint32_t freq,int16_t count)
    {
    	int16_t i;
    
    	for(i = 0;i<count;i++)
    	{
    	    *pbuff++ = (uint32_t)ceil(((float)(freq-PWM_START_FREQ))/(1.0+exp((-i*14.0/count)+7.0)));
    	}	
    }

    通过上面这个函数即可生成S曲线参数,函数的原型即为图6中的f(x)稍加变换。

    pbuff:存储S曲线参数        freq:你需要的最大频率        count:你需要的加速次数

    count越大,S曲线越平缓,加速过程越平滑,加速时间越长;count越小,S曲线越陡峭,加速过程越急剧,加速时间越短。

    有了S曲线生成函数后,我们需要做的就是在程序初始化时生成一组S参数:

    uint32_t CurveS_Para[500];

    CurveS_init(CurveS_Para,50000,500);

    这500个S曲线参数就是x∈[0,+500]之间取整时f(x)的值,把这500个点连接起来就成S型曲线。

    有了S参数,我们就可以调速了,速度调节函数如下:

    void SpeedAdjust(uint16_t count)
    {
    	Motor.Speed = CurveS_Para[count]*Motor.FrePropor/50+PWM_START_FREQ;    //计算本次速度
    	
    	htim1.Instance->ARR = TIM1_CLOCK_FREQ/Motor.Speed; 
    	htim1.Instance->CCR1 = (TIM1_CLOCK_FREQ/Motor.Speed)/2;
    	
    	if(Motor.Status == SPEED_INCREASE)            //加速
    	{
    		Motor.CountTemp++;
    	}
    	else if(Motor.Status == SPEED_DECREASE)       //减速
    	{
    		Motor.CountTemp--;	
    	}
    	Motor.Count = Motor.CountTemp*5/Motor.CountPropor;    //加速次数
    }

    count为加速次数,我们通过CurveS_Para[count]调用生成的S参数,Moto.FreqPropor为频率比例,MotorCountPropor为加速次数比例。由于我们是按照最大频率50kHz、最大加速次数500次(就是说加速500次频率可以达到50kHz)生成的S曲线,在实际使用过程中最大频率可能是任意值20kHz、30kHz等等,加速次数也可能是任意值200次、400次等等,但是我们只有一组S曲线参数,所以我们需要通过比例来对最大目标频率以及相应的加速次数做一下缩放。简单来说就是插值法,500个按照某个规律排列的数据中,每隔相同的间距取其中的一个数据组成一组新的数据,那么这组新数据应和原数据有相同的规律。

    我们现在有一个数组CurveS_Para[500],里面的的元素按照S型函数规律排列,如果每隔5/3的间距在这个数组中据中取出一个元素然后组成一个新的数组,那么我们可以在原数组中取300个元素,且组成的新数组里的元素同样是按照S型函数排列。而速度调节函数中的5/Motor.CountPropor就是我们的取数间隔,如果300次加速至最大值Motor.CountPropor=3即可。本次调速需要生效的频率值同样需要通过比例计算得到 CurveS_Para[count]*Motor.FrePropor/50,如果我们最大的目标频率为30kHz,Motor.FrePropor=30即可,当加速次数count达到最大499时,速度同样达到最大。

    速度调节函数中真正生效的是Motor.Count这个参数,在后面的调速中Motor.Count作为参数传递给SpeedAdjust()函数中。Motor.CountTemp是用来计算Motor.Count的一个中间变量,也是实际的已经加速的次。在加速过程中,每进行一次加速调用一次SpeedAdjus()函数,同时Motor.CountTemp++,记录加速次数。加速过程中频率按照S型规律由最小增到最大,对应的S参数CurveS_Para[0]增加到CurveS_Para[499]。而减速过程中频率同样按照S型规律由最大减至最小,参数CurveS_Para[499]减小到CurveS_Para[0],同时同时Motor.CountTemp--记录剩余的减速次数。简单的说,加速过中将CurveS_Para数组中的元素从前往后代入频率计算,减速过程中将CurveS_Para数组中的元素从后往前代入频率计算。OK,是不是很简单?

    最后看一下速度调节状态机,电机状态由 加速->匀速->减速 变化,在加速和减速阶段调用速度调节函数。加速过程中加速次数达到最大后停止加速转为匀速运动,至于什么时候开始减速由你实际的程序决定。

    void SpeedAdjustMachine(void)
    {
    	switch(Motor.Status)
    	{
    		/*加速*/						
    		case SPEED_INCREASE:
    			if(Motor.Count <= COUNT_MAX)
    			{
    				SpeedAdjust(Motor.Count); 
    			}
    			else
    			{
    				Motor.Status = SPEED_STABLE;
    			}
    			break;
    
    		/*匀速*/	
    		case SPEED_STABLE:
    			if(Motor.PWMcount >= (Motor.PWMneed-Motor.SpeedDecrPWM))
    			{		
    				Motor.Status = SPEED_DECREASE;	
    			}
    			break;
    				
    		/*减速*/	
    		case SPEED_DECREASE:
    			if(Motor.Count >= 0)
    			{
    				SpeedAdjust(Motor.Count); 
    			}
    							
    			if(Motor.PWMover == 1)
    			{		
    				HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_1);
    			}
    			break;
    				
    		default :
    			break;
    	}
    }

    速度调节状态接在另一个定时器的定时中断中调用,多久调节一次速度由该定时器的中断频率决定,500us、1ms、2ms都行。中断频率和加速次数共同决定电机的加减速时间,需要根绝电机能够承受的最大加速度调节加速次数以及调速时间间隔。

    最后再看一下实际的效果,每100us读取一次TIM1的自动重载值ARR,计算当前PWM脉冲,生成的S曲线图像:

    实际的加减速过程

    通过示波器可以观察到频率由大减小,由小增大的一个过程。

    展开全文
  • 终于搞定了sm2算法在smt32单片机上的移植。 之前的动态内存分配,在上面总是莫名其妙的崩。不知道堆和栈空间该改多大合适。且总共64K的内存,太受限了。 几次想放弃,最后还是坚持了一下,终于搞定啦! 看miracl...
  • 连接:http://bbs.21ic.com/icview-170880-1-1.html -------------------------------------------------以下为原文 ------------------- 连接:...单片机大多资源小,算法占用
  • 非常流行的步进电机STM32控制代码,S型加减速,代码中可以随时获取电机已走脉冲(实际就是当前位置),可以通过给定步数走指定距离,里面有相关程序说明。
  • 本设计以51系列单片机STC89C52为控制核心,以液晶显示模块、键盘为人机接口,负温度系数热敏电阻器(NTC)为采样传感器,实现了一个基于51单片机的热敏电阻测温设计。
  • 51单片机播放音乐(三):PWM播放任意波形原理PWM仿真电路图音频文件转成PWM代码单片机代码仿真输出波形 本文完整源码 原理 PWM 详细的参考这篇文章,这里简单说一下 脉冲宽度调制(PWM)的基本原理是冲量相等而形状...
  • 本文设计了一个基于AT89S52单片机控制的水温控制系统, 由下位机、上位机、和通讯网络三部分组成。下位机是基于单片机AT89S52和温度传感器DS18B20的高精度数据采集系统,功能是对温度的检测与输出控制。上位机采用...
  • 不过有些朋友可能只听说他叫单片机,他的全称是什么也许并不太清楚,更不用说他的英文全称和简称了。单片机是一块在集成电路芯片上集成了一台有一定规模的微型计算机。简称为:单片微型计算机或单片机 (Single Chip...
  • 单片机C简单编程范例

    2012-06-25 23:45:30
     相信很多爱好电子的朋友,对单片机这个词应该都不会陌生了吧。不过有些朋友可能只听说他叫单片机,他的全称是什么也许并不太清楚,更不用说他的英文全称和简称了。单片机是一块在集成电路芯片上集成了一台有一定...
  • 单片机心得

    2010-01-03 00:14:00
    相信很多爱好电子的朋友,对单片机这个词应该都不会陌生了吧。不过有些朋友可能只听说他叫单片机,他的全称是什么也许并不太清楚,更不用说他的英文全称和简称了。单片机是一块在集成电路芯片上集成了一台有一定规模...
  • 注意:接收的16进制要符合一定格式,才能读取显示,具体见程序 分了五个.m文件(一个主函数,四个子函数): main: %% clc; clear; global t1; global t2; global t3; global t4;...globa...
  • 单片机C51学习心得_01

    2008-11-14 22:31:00
    相信很多爱好电子的朋友,对单片机这个词应该都不会陌生了吧。不过有些朋友可能只听说他叫单片机,他的全称是什么也许并不太清楚,更不用说他的英文全称和简称了。单片机是一块在集成电路芯片上集成了一台有一定规模...
  • 莫名想起小时候电脑开机自检时的声音 当然平时洗衣机空调什么的也有 蜂鸣器原理就是两个铁片让其中一个的一端正负极不停的变换从而拍击另一个产生声音 蜂鸣器分有源和无源蜂鸣器 有源指的是外部只需要供电即可响...
  • 关注【电子开发圈】微信公众号,一起学习吧!...电子DIY、Arduino、51单片机、STM32单片机、FPGA…… 电子百科、开发技术、职业经验、趣味知识、科技头条、设备拆机…… 点击链接,免费下载100G+电子设计学习资料! ...
1 2 3 4 5 ... 20
收藏数 904
精华内容 361
关键字:

单片机s曲线计算