基于STM32裸机自动定位测试仪开发日记(一)
基于STM32裸机自动定位测试仪开发日记(一)
_hellc Copy于2018年8月份我自己的日记
备注
- 这是我在CSDN更新的第一篇文章,本文原文是我大二本科就读期间在上海某科技公司实习期间为公司做一台具有XYZ三轴定位,同时可以转发万用表等的数据至上位机的ATE系统的每日开发日志(当时记录在作业部落Markdown里),删除了相关公司信息、发牢骚的心情记录后复制粘贴、上传。
- 更新本文的主要目的是记录一个初次做项目的小小本科僧的成长历程(那一个月真的成长好多啊),以及面对工程问题时我的一些初步思考。纯技术上,真的很入门很基础,而且我贴的大部分代码是初版,很多都在后来的使用过程中被淘汰了。当然,有一些简单的编程思想和坑。
- 这不是一篇技术笔记!这不是一篇技术笔记!这不是一篇技术笔记!!里面有很多我在这一月实习里的感想。
- 后期实现的花里胡哨的功能(单片机通过RS232控制万用表、单片机开机自检动画显示balabala)贴的较少,因为后来就有点懒了没有天天写笔记- -。
-
本项目还配有Qt上位机设计,如果有需要准确的嵌入式代码/Qt源码的可以评论区留言。
最后系统效果:
没找到不带人的照片2333
乱成一团的理线区域,测试版是这样的啦
2018.7.24 周二
- 确定xyz轴定位的机械结构,选择stm32作为MCU,选择Qt作为上位机开发环境,与导师讨论相关方案并通过
- 购买相关仪器
- 了解到多数开发仪器使用RS232串口通信协议,购入相关转换芯片
系统框图
进入硬件开发阶段
2018.7.25 周三
得到开发周期
title 项目开发流程
section 开发阶段
人机交互 :2018-07-23, 5d
空间定位 :2018-07-23, 5d
速度优化 :2018-07-30, 5d
数据读取 :2018-07-30, 5d
section 整合验收
系统整合 :2018-08-06, 12d
验收 :2018-08-13, 5d
- 通过普通端口控制产生电机控制信号
- 接受电脑端uart
- 位移控制
备忘录
要实现精确及高速控制,必须使用PWM控制,避免占用cpu时间。
这个优化应该在第二周,基本数据采集完成后进行。
目前完成的代码
#include "public.h"
#include "printf.h"
#include "adc.h"
#include "systick.h"
#include "pwm.h"
#include "usart.h"
/*******电机控制口定义***********/
#define stepcontrol1 GPIO_Pin_0
#define stepdirection1 GPIO_Pin_1
#define stepcontrol2 GPIO_Pin_2
#define stepdirection2 GPIO_Pin_3
#define stepcontrol3 GPIO_Pin_4
#define stepdirection3 GPIO_Pin_5
#define stepcontrol4 GPIO_Pin_6
#define stepdirection4 GPIO_Pin_7
extern u16 Rx;
extern u16 Tx;
/****************************************************************************
* Function Name : step_init
* Description : 电机控制端口初始化,使用GPIOC
* Input : None
* Output : None
* Return : None
****************************************************************************/
void step_init()
{
GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量,用来初始化GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); /* 开启GPIO时钟 */
/* 配置GPIO的模式和IO口 */
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_All; //使用GPIOC作为控制口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //设置推挽输出模式
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
GPIO_Init(GPIOC,&GPIO_InitStructure); /* 初始化GPIO */
GPIO_SetBits(GPIOC,GPIO_Pin_All);
}
/****************************************************************************
* Function Name : Step_Control
* Description : 电机控制函数
* Input : |dir:方向 | period:延迟时间 | step:步数 | choose:电机选择|
* Output : None
* Return : None
****************************************************************************/
void Step_Control(u8 dir,u32 period,u32 steps,u8 choose)
{
u32 i;
if (choose==0x00)
{
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection1);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection1);
}
for(i=0; i <= steps;i++)
{
GPIO_SetBits(GPIOC,stepcontrol1);
delay_ms(period);
}
}
if (choose==0x01)
{
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection2);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection2);
}
for(i=0; i <= steps;i++)
{
GPIO_SetBits(GPIOC,stepcontrol2);
delay_ms(period);
}
}
if (choose==0x02)
{
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection3);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection3);
}
for(i=0; i <= steps;i++)
{
GPIO_SetBits(GPIOC,stepcontrol3);
delay_ms(period);
}
}
if (choose==0x03)
{
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection4);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection4);
}
for(i=0; i <= steps;i++)
{
GPIO_SetBits(GPIOC,stepcontrol4);
delay_ms(period);
}
}
}
extern u8 tt;
/****************************************************************************
电机控制通信协议
第0位:是否进行位置控制,1是,0否
第1位:方向,0正1反
第2,3,4,5位:速度,越大越慢
第6,7,8,9,10,11,12,13位:移动的步数,最大单次255步
第14,15位:电机选择
****************************************************************************/
int main()
{
u16 state=0;
u16 dir,per,step;
u8 mot;
usart_init();//uart通信初始化
adc_init(); //ADC初始化
printf_init(); //printf初始化
pwm_init();
step_init();
while(1)
{
state=0;
if(tt==2)
{
state=Rx;
state&=0x8000;
state>>=15;
if(state!=1)
{
dir=Rx&0x4000;
dir>>=14;
per=Rx&0x3c00;
per>>=10;
step=Rx&0x03fc;
step>>=2;
mot=Rx&0x0003;
Step_Control(dir,per,step,mot);
}
tt=0;
}
}
extern u16 Rx;
extern u16 Tx;
extern u8 tt;
void USART1_IRQHandler(void) //串口1中断函数
{
static u8 k;
USART_ClearFlag(USART1,USART_FLAG_TC);
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=Bit_RESET)//检查指定的USART中断发生与否
{
k=USART_ReceiveData(USART1);
Rx<<=8;
Rx=k;
tt=tt+1;
//USART_SendData(USART1,k);//通过外设USARTx发送单个数据
//while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==Bit_RESET);
}
}
}
/****************************************************************************
这里是位于stm32f10x_it.c中的串口中断函数
****************************************************************************/
extern u16 Rx;
extern u16 Tx;
extern u8 tt;
void USART1_IRQHandler(void) //串口1中断函数
{
static u8 k;
USART_ClearFlag(USART1,USART_FLAG_TC);
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=Bit_RESET)//检查指定的USART中断发生与否
{
k=USART_ReceiveData(USART1);
Rx<<=8;
Rx=k;
tt=tt+1;
//USART_SendData(USART1,k);//通过外设USARTx发送单个数据
//while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==Bit_RESET);
}
}
部分引脚初始化代码忽略
由于uart通信协议一次只能传递一个字(8个字节),而一个完整的电机控制指令需要16个字节,故用位操作的方式将两个连续的8字节数据合成一个16字节数据。这样设计可以利用已有的通信协议,并且减轻上位机编写难度。缺点是要注意合并逻辑,避免字节错位执行。
备忘录
后期可以通过加入开始位和结束位来避免字节错位
2018.7.26 周四
了解C++相关知识,协助上位机开发
2018.7.27 周五
- 完成pwm配置
Keil可以使用软件仿真对stm32进行仿真,在分析器里端口用PORTA.0的格式
以pwm方式调节步进电机的基本思路
重点代码如下:
/****************************************************************************
* Function Name : TIM3_IRQHandler
* Description : 计时器中断处理
* Input : None
* Output : None
* Return : None
****************************************************************************/
void TIM3_IRQHandler() //定时器3中断函数
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
dangqian++;
if(dangqian==step)
{
TIM_Cmd(TIM3, DISABLE);
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}
/****************************************************************************
* Function Name : Step_Control
* Description : 电机控制函数
* Input : |dir:方向 | period:延迟时间 | step:步数 | choose:电机选择|
* Output : None
* Return : None
****************************************************************************/
void Step_Control(u8 dir,u32 period,u32 steps,u8 choose)
{
u16 mm;
dangqian=0;
if(per<20) per=20;
mm=1000000/per-1;
TIM_SetAutoreload(TIM3, mm);
TIM_SetCompare2(TIM3, mm>>1);
TIM_Cmd(TIM3, ENABLE);
…………
旁听一次公司内部培训
培训人:公司创始人、CTOxx
主题:MOS器件失效分析
感受:作为工程人员,有必要了解器件的内部结构和基本原理,熟悉工程经验分析的方法,才能更快地分析出问题的原因。丰富的工程经验确实是有资历的技术人员价值高的重要原因。
- xx科技作为以美国科技公司架构为参考的starup公司,结构分工扁平化,上下级之间领导关系简单,技术人员基本平等。
- xx芯片的特点要求其必须有相当部分外围设计人员,这也是为什么研发岗位和应用岗位人数接近的原因。
- 半导体公司除研发、市场、行政岗位外,通常还有运营部门,包括芯片测试部门,要在芯片制造的多个层次对芯片进行测试,主要是为了使产品质量能被市场以更加直观、可信的方式接受。同时还有FE岗位,负责与客户进行技术接洽,帮助客户完成应用开发。
- 研发岗位可向系统工程师流动,应用岗位可向FE流动,测试岗位也有很多流动方向。
- 时间管理很重要。可以将每天要做的事列一个清单,包含完成所需要的时间。
- 在美国创业机会较多,加入小公司更加具有挑战性,大公司通常业务模式固定,进步速度较慢。
2018.7.28 周六
- 实现多路PWM同时工作
要点:
使用
if (TIM_GetITStatus(TIM3, TIM_IT_CC) != RESET)
可以在同一个中断TIM3_IRQHandler()里获取各通道PWM的状态
具体代码如下:
/****************************************************************************
* Function Name : TIM3_IRQHandler
* Description : 计时器中断处理
* Input : None
* Output : None
* Return : None
****************************************************************************/
void TIM3_IRQHandler() //定时器3中断函数
{
if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
dangqian1++;
if(dangqian1==mubiao1)
{
TIM_CCxNCmd(TIM3,TIM_Channel_1, DISABLE);
dangqian1=0;
}
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
{
dangqian2++;
if(dangqian2==mubiao2)
{
TIM_CCxNCmd(TIM3,TIM_Channel_2, DISABLE);
dangqian2=0;
}
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC3) != RESET)
{
dangqian3++;
if(dangqian3==mubiao3)
{
TIM_CCxNCmd(TIM3,TIM_Channel_3, DISABLE);
dangqian3=0;
}
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC4) != RESET)
{
dangqian4++;
if(dangqian4==mubiao4)
{
TIM_CCxNCmd(TIM3,TIM_Channel_4, DISABLE);
dangqian4=0;
}
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
/****************************************************************************
* Function Name : Step_Control
* Description : 电机控制函数
* Input : |dir:方向 | period:延迟时间 | step:步数 | choose:电机选择|
* Output : None
* Return : None
****************************************************************************/
void Step_Control(u8 dir,u32 period,u32 steps,u8 choose)
{
u16 mm;
if(per<20) per=20;
mm=1000000/per-1;
TIM_SetAutoreload(TIM3, mm);
TIM_Cmd(TIM3, ENABLE);
TIM_SetCompare2(TIM3, mm>>1);
if (choose==0x00)
{
mubiao1=steps;
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection1);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection1);
}
}
if (choose==0x01)
{
mubiao2=steps;
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection2);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection2);
}
}
if (choose==0x02)
{
mubiao3=steps;
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection3);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection3);
}
}
if (choose==0x03)
{
mubiao4=steps;
if (dir==0)
{
GPIO_SetBits(GPIOC,stepdirection4);
}
else
{
GPIO_ResetBits(GPIOC,stepdirection4);
}
}
}
注意这个时候初始化代码里就不要使能pwm了
- 实现uart同时传递多组位移信息
- 研究直接发送相对位置与绝对位置的可行性
2018.7.30 周一
之前的多路PWM逻辑有问题,暂时用回端口控制
经过测试,16位电机操作指令可以基本满足设计要求,现在主要进行上位机参数优化
2018.7.31 周二
目前的任务在于提高uart的效率,为高速电机控制和将来的多路数据传输做准备(如果继续使用端口控制,数据传输的cpu时间很有可能凑不出来,之后还是要研究pwm)
stm32串口环形缓冲队列处理
目的:避免传输丢帧,提高传输效率
环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。
“环形缓冲区”数据接收处理机制的好处在于:利用了队列的特点,一头进,一头出,互不影响,在数据进去(往里存)的时候,另一边也可以把数据读出来,而读出来的数据,留下的空位,又可以增加多的存储空间,从而避免一边接收数据且一边处理数据会在数据量密集的时候而导致的丢掉数据或者数据产生冲突的问题。
如果仅有一个线程读取环形缓冲区的数据,只有一个串口往环形缓冲区写入数据,则不需要添加互斥保护机制就可以保证数据的正确性。
2018.8.1 周三
- 完善三*度电机控制系统
- 借助mcu将万用表数据转发至上位机
对于上位机对下位的控制,我们发现,由于每个通信均采用中断方式处理,这样在执行一条指令时如果有信息传来,就会发生严重的错误。比如:
为了避免AB指令的冲突,我们可以采取两种方式:
- 被动式:上位机估算A指令执行完成的时间,在执行完成后再发送B指令;
- 交互式:下位机执行完A指令后,向上位机发送一个执行完成的标记,上位机检测到该标记后再发送B指令。
我们首先尝试了被动式的方法,发现该方法不宜在这种情况下使用,因为估算时间一旦小于实际时间,就一定会造成错误。而如果给等待时间大量余量,则违背了高速运动的设计目标。
最后,我们采取了交互式的方法,在命令执行完成后,由下位机向上位机发送字符“a”作为结束符,实现了设计目标。
数据转发思路:
使用两个uart口,在每一个uart口的中断中将收到的字符直接通过另一个口发送出去,即可实现最大效率的转发,不占用多余的CPU时间。
void USART2_IRQHandler(void)
{
//USART_ClearFlag(USART2,USART_FLAG_TC);
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=Bit_RESET)//检查指定的USART中断发生与否
{
***u8 k=USART_ReceiveData(USART2);***
uart1_send_char(k);
USART_ClearFlag(USART2,USART_FLAG_TC);
}
}
2018.8.2 周四
- 将万用表的数据进行格式化处理
- 实现运动与数据读取的协同化运作
万用表发送的数据中有换行符“0X0D”,以及“=”、“>"通过在转发中断中加入字符判断,可以高效过滤。
void USART2_IRQHandler(void)
{
//USART_ClearFlag(USART2,USART_FLAG_TC);
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=Bit_RESET)//检查指定的USART中断发生与否
{
u8 k=USART_ReceiveData(USART2);
if((k!='>')&&(k!='=')&&(k!=0x0A)&&(k!=0x0D))
{
uart1_send_char(k);
}
USART_ClearFlag(USART2,USART_FLAG_TC);
}
}
2018.8.3 周五
一切顺利,上下位机配合完美,编写了简单的GUI界面
2018.8.6 周一
本周起,因为主程序代码太多了,就不贴源程序了。
今天机械框架终于到了,我们用了一整天的时间尝试把机械框架搭建起来,却发现零件不够????
2018.8.7 周二
由于昨天没能成功搭建起硬件框架来,我们暂时完成了对x轴的测试。目前,驱动电路可以正常工作,步进电机在9v电压驱动下,最大电流为数百毫安,两个电机共同工作预计也不会超过1A,这位将来的电源选择提供了参考。
由于没有完整的机械结构,我又把工作重心放在了GUI界面的设计上。今天我彻底理解了在屏幕上手工进行3D绘制的方法。通过坐标转换,将三维坐标投影在二位平面上,就可以实现3维X显示。由于之前已经实现了对三维坐标的记录,目前三维绘制的工作也比较顺利,其本质不过是二维点的绘制。
正交直角坐标系下的坐标投影关系非常简单:
x1=(int)(60+y2-0.707*x2/2);
y1=(int)(300-z2+0.707*x2/2);
具体的绘图函数保存在了8.7版的 lcd3d() 函数中
另外,今天还第一次尝试了动画的绘制。
通过在while函数里写入以下内容,巧妙地实现了一个小方块从左到右滑动,示意等待:
LCD_Fill(i,350,i+10,354,BLUE);
LCD_ShowString(140,335,tftlcd_data.width,tftlcd_data.height,12,"Waiting command");
delay_ms(4);
LCD_Fill(i,350,i+10,354,WHITE);
基本原理很简单,就是不断地绘制一个坐标向右加1的方块并把上一个涂成白色(顺序很重要,一定要在新的方块绘制以后,新的坐标生成以前。如果while函数里还有别的东西,消除语句应该紧接着坐标更新函数放在坐标更新语句的前面,使画面变成白色又没有新内容绘制的时间最短)
2018.8.8 周三
今天我把动画绘制运用的炉火纯青= =
同时,为了提高产品的可用性,我为下位机加入了启动界面,可以显示系统的加载与运行状态,有利于用户的使用。
在此过程中,我学会了使用几个辅助小工具将图片和文字转换成二进制点阵,并显示在屏幕上。软件保存在普中stm32提供的辅助开发小工具文件夹下。
2018.8.9 周四
机械框架终于基本搭好了,今天我们共同测试了xyz轴的运行状态,目前看来一切良好,最大电流依然没有超过1A,电源压力很小。驱动芯片表面温度最高达到了54度,可以考虑加上散热片。
在测试过程中,我发现之前的3d实时绘图在32孱弱的性能下,竟然拖慢了电机的运行。无奈之下只好去掉了这个功能。
2018.8.10 周五
今天我们初步进行了电机步数与实际距离之间的关系,用上了触点开关,实现了电机的自动复位。
初步探讨了芯片与手机的固定方法,用3D建模软件绘制了理想中的夹具模型。
先贴到这里,有机会再填坑