完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>

|
` 本帖最后由 zzq宁静致远 于 2013-11-21 18:37 编辑 8月初以来,在论坛,“朱兆祺带你一步一步学习嵌入式(连载)”(https://bbs.elecfans.com/jishu_357014_1_1.html)以及“朱兆祺教你如何攻破C语言学习、笔试与机试的难点(连载)”(https://bbs.elecfans.com/jishu_354666_1_1.html)得到大家的认可,说明这两个帖子对大家有很大的帮助。这两个帖子都是我接触C语言以来到现在承接单片机、ARM项目的经验总结,我希望通过这两个帖子,各位嵌入式学习者能得到正能量,以最快的方式进步。C语言这个帖子我已经引领网友进入实战项目中,我的C语言功底不是单纯传授书本知识,更重要的是能让各位领略到我这个帖子对你做项目到底有多大的用处。 网友认为,朱兆祺是不是只会编程,只会C语言,只会Linux。OK,朱兆祺趁这个国庆假期,我想此刻很多网友都在游历祖国山河,我就将使用STM32做过的项目小做一下整理,由浅入深、深入浅出,告诉你一个从未接触过STM32的学习者,怎么去做好一个项目,怎样可以代码重用,怎样给项目预留好升级空间。既然是教大家,就使用STM32最强大的那款(STM32F103ZET6、144个引脚),以这个为控制芯片。其实只要你做好模块化编程,控制芯片早已不重要(为什么?后面你会恍然大悟的)。 在进行STM32学习过程中,你们手中最好有一块STM32板子: http://item.taobao.com/item.htm?spm=a1z10.1.w137712-3105488089.8.X83zzd&id=22208871338 购买的时候告诉客服你是看到“朱兆祺STM32项目(硬件设计、软件编程)手记(连载)”购买的,只需要49元,但是每人仅能购买一块。 朱兆祺STM32项目(硬件设计、软件编程)手记(连载)正式开始。 朱兆祺STM32项目手记目录: `
第一节 STM32核心板介绍: https://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=385613&pid=2309703&fromuid=222350 第二节 STM32小试牛刀——LED: https://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=385613&pid=2309723&fromuid=222350 第三节 UART——调试帮手: https://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=385613&pid=2309734&fromuid=222350 第四节 直流电机PID控制: https://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=385613&pid=2313310&fromuid=222350 第五节 直流电机PID控制程序: https://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=385613&pid=2330272&fromuid=222350 第六节 指针函数在STM32程序中的巧妙应用 https://bbs.elecfans.com/forum.p ... ;amp;fromuid=222350 第七节 PID在智能小车中的应用 https://bbs.elecfans.com/forum.ph ... 2926&fromuid=222350 第八节 关于BOOT问题 https://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=385613&pid=2420842&fromuid=222350
|
|
相关推荐
2 条评论
190 个讨论
|
|
|
第五节 直流电机PID控制程序
对于PID调节,大家都可能知道他的口诀: 参数整定找最佳,从小到大顺序查 先是比例后积分,最后再把微分加 曲线振荡很频繁,比例度盘要放大 曲线漂浮绕大湾,比例度盘往小扳 曲线偏离回复慢,积分时间往下降 曲线波动周期长,积分时间再加长 曲线振荡频率快,先把微分降下来 动差大来波动慢,微分时间应加长 理想曲线两个波,前高后低4比1 一看二调多分析,调节质量不会低 现在我们将这个口诀应用到程序,先定义PID数据的结构体: /********************************************************************************* 定义一个PID结构体 *********************************************************************************/ typedef struct __PID_ { int SetPoint; // 给定值 // int nextPoint; // 上次的 int Kp; // 比例常数 Proportional Const int Ki; // 积分常数 Integral Const int Kd; // 微分常数 Derivative Const int LastError; // Error[-1] int PrevError; // Error[-2] int SumError; // Sums of Errors int LastOut; // 上次的输出量 int MaxOut; int MinOut; } PID; PID控制的实现程序: /********************************************************************************* 函数名:PID_Aaccount 函数功能:进行PID增量式结算 输入参数:*PP--指向需要结算的PID ,NextPoint当前的反馈值 输出参数:PID结算后的给定值 *********************************************************************************/ int PID_Account( PID *pp, int NextPoint ) { int Error; int Add_out=0; Error = pp->SetPoint - NextPoint; // 偏差 // Add_out = (pp->Kp*(pp->LastError - pp->PrevError) // + pp->Ki*Error // + pp->Kd*(Error-2*pp->LastError+pp->PrevError))/1000; Add_out = (pp->Kp*(Error - pp->LastError) + pp->Ki*Error + pp->Kd*(Error-2*pp->LastError+pp->PrevError))/1000; pp->PrevError = pp->LastError; //存储上次偏差 pp->LastError = Error; //存储本次偏差 pp->LastOut = pp->LastOut + Add_out; //存储本次输出 if (pp->LastOut > pp->MaxOut){ pp->LastOut = pp->MaxOut; } else if (pp->LastOut < pp->MinOut) { pp->LastOut = pp->MinOut; } return (pp->LastOut); } /********************************************************************************* 函数名:PID_Addaccount 函数功能:进行PID置位式结算 输入参数:*PP--指向需要结算的PID ,NextPoint当前的反馈值 输出参数:PID结果 *********************************************************************************/ int PID_AddAccount( PID *pp, int NextPoint ) { int dError,Error; Error = pp->SetPoint - NextPoint; // 偏差 pp->SumError += Error; // 积分 dError = pp->LastError - pp->PrevError; // 当前微分 pp->PrevError = pp->LastError; //存储上次偏差 pp->LastError = Error; //存储本次偏差 return (pp->Kp * Error/1000 // 比例项 + pp->Ki * pp->SumError/1000 // 积分项 + pp->Kd * dError/1000 // 微分项 ); } /********************************************************************************* 函数名:PIDInit 函数功能:PID数据初始化 输入参数:*pp需要初始化的PID 输出参数: *********************************************************************************/ void PIDInit (PID *pp) { pp->SetPoint = 0; // 给定值 pp->Kp= 0; // 比例常数 Proportional Const pp->Ki= 0; // 积分常数 Integral Const pp->Kd= 0; // 微分常数 Derivative Const pp->LastError= 0; // Error[-1] pp->PrevError= 0; // Error[-2] pp->SumError= 0; // Sums of Errors pp->LastOut= 0; // 上次的输出量 pp->MaxOut = 0; pp->MinOut = 0; } 这是PID控制直流电机的核心算法。这个算法让我屡试不爽,在校期间历经2次省赛、1次国赛,毕业后在实际项目中使用一次,效果绝佳。亲,好好研究下,让你的电机控制更精准更完美。 |
|
|
|
|
|
|
|
|
加油啊,热心人已经很少了
|
|
|
|
|
|
|
|
|
本帖最后由 zzq宁静致远 于 2013-10-14 14:49 编辑
第六节 指针函数在STM32程序中的巧妙应用 在10月12号C语言技术公开课第二讲数组和指针的恩恩怨怨课堂中(https://bbs.elecfans.com/jishu_361304_1_1.html),我给大家引出了指针好书如何去避免成程序中产生全局变量,为什么要避免全局变量的产生?第一,可读性;第二,可移植性。在我的成程序中,是很少产生全局变量的,因为我的代码移植性很强,在STM32中的应用程序在单片机、A8中照样可以使用。 在这里,我暂且写一个STM32的AD采样程序,说明怎么避免全局变量的产生。 同样,我们将与地层打交道的配置全部放在configuration.c文件中,并且在AD1_Configuration()函数中将ADC1的配置完成,之后我们就不需要在管ADC1的配置,将底层和应用层彻底隔离开来。 /******************************************************************** ** 函数名称:AD1_Configuration ** 函数功能:对A/D采样的配置 ** 入口参数:None ** 出口参数:None ********************************************************************/ void AD1_Configuration(void) { vu16 ADCConvertedValue; GPIO_InitTypeDef GPIO_InitStructure; DMA_InitTypeDef DMA_InitStructure; ADC_InitTypeDef ADC_InitStructure; /* 使能DMA1时钟 */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* 使能ADC1和GPIOC时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE); /* 使能GPIOx时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx,ENABLE); /* 以下配置为ADC引脚的GPIO功能配置 */ /* PC3引脚作为ADC1功能 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOC, &GPIO_InitStructure); /* 以下配置为DMA配置 */ /* 选择DMA1的通道1 */ DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(&ADCConvertedValue); DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); /* 使能DMA1的通道1 */ DMA_Cmd(DMA1_Channel1, ENABLE); /* ADC1配置 */ ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; /* 初始化 */ ADC_Init(ADC1, &ADC_InitStructure); /* ADC1中通道13 */ ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 1, ADC_SampleTime_55Cycles5); /* 使能ADC1 DMA */ ADC_DMACmd(ADC1, ENABLE); /* 使能ADC1 */ ADC_Cmd(ADC1, ENABLE); /* 复位ADC1校准状态寄存器 */ ADC_ResetCalibration(ADC1); /* 获取ADC1复位校准状态寄存器 */ while(ADC_GetResetCalibrationStatus(ADC1)); /* 开始ADC1校准程序 */ ADC_StartCalibration(ADC1); /* 获取ADC1校准状态 */ while(ADC_GetCalibrationStatus(ADC1)); /* 开启ADC1软件转化功能 */ ADC_SoftwareStartConvCmd(ADC1, ENABLE); } 配置完成之后,我们需要进行ADC采样,这个在主函数中进行: while (1) { …… /* 获取ADC1的采样值 */ AD_Value = ADC_GetConversionValue(ADC1); /* 发送数据 */ UART1Write((unsigned char *)(ADC1_Apply(AD_Value)), 4); UART1Write((unsigned char *)"rn", sizeof("rn")); /* 释放申请的内存 */ free(ADC1_Apply(AD_Value)); …… } 重点出来了:(ADC1_Apply(AD_Value)),这是返回ADC采样数据转化为字符串的数组地址,那么,这个到底是什么呢? /******************************************************************** ** 函数名称:ADC1_Apply ** 函数功能:ADC1采样转换,使用指针函数,避免AD_Buff使用全局变量 ** 入口参数:int iData ** 出口参数:AD_Buff ********************************************************************/ unsigned char *ADC1_Apply(int iData) { /* 开辟一个5字节的空间 */ unsigned char *AD_Buff = (unsigned char *)malloc(5); /* int类型转换为char,便于UART1发送数据 */ AD_Buff[0] = (iData / 1000 % 10) + 0x30; AD_Buff[1] = (iData / 100 % 10) + 0x30; AD_Buff[2] = (iData / 10 % 10) + 0x30; AD_Buff[3] = (iData % 10) + 0x30; AD_Buff[4] = 0; /* 返回AD_Buff数组的地址 */ return AD_Buff; } 这里通过指针函数,在unsigned char *ADC1_Apply(int iData)函数中开辟一个空间:unsigned char *AD_Buff = (unsigned char *)malloc(5);将int型数据转化为char型数据,并且存放在AD_Buff这个地址的空间中,在最后返回这个地址给主函数。 为了方便大家理解,我单独提取出指针函数这个部分: #include #include "malloc.h" unsigned char *ADC1_Apply(int iData) { unsigned char *AD_Buff = (unsigned char *)malloc(5); AD_Buff[0] = (iData / 1000 % 10) + 0x30; AD_Buff[1] = (iData / 100 % 10) + 0x30; AD_Buff[2] = (iData / 10 % 10) + 0x30; AD_Buff[3] = (iData % 10) + 0x30; AD_Buff[4] = 0; return AD_Buff; } int main(int argc, char *argv[]) { printf("%cn", *(ADC1_Apply(86))); printf("%cn", *(ADC1_Apply(86) + 1)); printf("%cn", *(ADC1_Apply(86) + 2)); printf("%cn", *(ADC1_Apply(86) + 3)); printf("%cn", *(ADC1_Apply(86) + 4)); return 0; } 输出为: 0 0 8 6 我想这样,大家应该很清楚了。朱兆祺在此再一次提醒大家,为了你的程序的可读性和可移植性,请别动不动就整出一个全局变量。C语言的功能博大精深,指针的威力更是超出你的想象。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
你正在撰写讨论
如果你是对讨论或其他讨论精选点评或询问,请使用“评论”功能。
STM32F405驱动DS1302时钟模块,输出时间错乱该怎么排查?
2810 浏览 2 评论
stm32f405rgt6驱动DS1302ZN出现时间错乱问题
2420 浏览 1 评论
stm32用fsmc读取ad7606采集数据,数据不变,只有开发版复位才更新数据
2288 浏览 0 评论
2404 浏览 1 评论
1643 浏览 1 评论
/9
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-12-2 15:50 , Processed in 0.991445 second(s), Total 82, Slave 74 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191

淘帖
10147