【TI-RSLK 中国版试用报告】+ 基础上手与视觉巡线

[复制链接]

2

主题

3

帖子

56

积分

一粒轻沙

Rank: 1

积分
56
查看: 233回复: 2 发表于 2019-3-5 11:24:00   只看该作者
本帖最后由 lu0x69 于 2019-3-5 17:02 编辑




视频链接:项目视频

9F84D528A5CFA05E5806F2A4232139BD.jpg

以前对于嵌入式接触最多的也就是意法半导体公司的STM32F系列,以及NXP公司的KeritisM4系列的芯片。第一次接触TI公司的微控制器。虽说理论上对于微处理器的控制都是大同小异,但是在调试MSP432的时候还是遇到了一大堆的trouble。第一次入手MSP EXP432P401R LaunchPad时候,还真是一脸懵。怎么下载程序?用什么开发环境?由于本人对嵌入式编程实战经验也不多,所以对开发环境了解也并不多,在TI官网上了解到CCS,我接触过MDK, IAR也就这些了。但是迫于开发调试时间限制,我最终选择了相对熟悉的Keil5开发环境。
第一部分:开发环境部署

在Keil官网完美的找到了MSP432pack包。但是这个过程对于一个英语小白可头疼了,废话不多说,直接附上下载链接:pack下载链接
对于MSP EXP432P401R LaunchPad 这个开发板,Keil内部集成了TI XDS Debugger调试仿真探针。配置选择如下:

图1

图1
对于并不精通嵌入式的小白,寄存器操作总会让人抓狂。相对简单的方法就是官方下载MSP432固件库:固件库链接
2.png

基于这些c文件与头文件配置KEIL工程就不在啰嗦了。官方下载的历程里边也有keil的空白工程。
有了以上的基础,开发起来就相对轻松了。虽然“TI 机器人学习套件”网站里边给了相关模块控制原理以及里程,但是全都是基于寄存器编写的。所以我还是决定选择自己编写基于固件库的控制代码。
第二部分:固件库学习配置
对于固件库的学习,TI官方网站提供了Driverlib API Guide文件,对各个API函数的详细讲解。包括输入参数,结构体定义,函数返回值等等。基于这篇文档,基础的操作也就应该会一半了。
3.png

对于一辆小车,第一反应就应该是让电机转起来。MSP432微处理器,timea定时器模块可以产生PWM波以及输入捕获。但是对于这个硬件连接已经固定的小车,去寻找它连接的引脚是一件麻烦事。最终在官方提供的教学PPT里边找到了相应的引脚,以后别的模块引脚号也是由此得知,连接如下:资料链接
4.png

电机PWM配置程序如下:
  1. #define  timerPeriod1   100        //周期
  2. #define  dutyCycle1     0         //占空比
  3. #define  timerPeriod2   100        //周期
  4. #define  dutyCycle2     0        //占空比
  5. //**************************************
  6. /* Timer_A PWM Configuration Parameter */
  7. Timer_A_PWMConfig pwmConfig0_3 =
  8. {
  9.     TIMER_A_CLOCKSOURCE_SMCLK,           //时钟源
  10.     TIMER_A_CLOCKSOURCE_DIVIDER_4,        //分频
  11.     timerPeriod1,                             //周期
  12.     TIMER_A_CAPTURECOMPARE_REGISTER_3,   //选择捕获寄存器通道
  13.     TIMER_A_OUTPUTMODE_TOGGLE_SET,       //翻转/置位
  14.     dutyCycle1                              //占空比
  15. };
  16. //**************************************
  17. /* Timer_A PWM Configuration Parameter */
  18. Timer_A_PWMConfig pwmConfig0_4 =
  19. {
  20.     TIMER_A_CLOCKSOURCE_SMCLK,            //时钟源
  21.     TIMER_A_CLOCKSOURCE_DIVIDER_4,         //分频
  22.     timerPeriod2,                             //周期
  23.     TIMER_A_CAPTURECOMPARE_REGISTER_4,   //选择捕获寄存器通道
  24.     TIMER_A_OUTPUTMODE_TOGGLE_SET,       //翻转/置位  
  25.     dutyCycle2                           //占空比
  26. };
  27. void TimaInit(void)
  28. {
  29. //配置GPIO
  30.     GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P2, GPIO_PIN6, GPIO_PRIMARY_MODULE_FUNCTION);//P2.6
  31.     GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P2, GPIO_PIN7, GPIO_PRIMARY_MODULE_FUNCTION);
  32.     Timer_A_generatePWM(TIMER_A0_BASE, &pwmConfig0_3);    //PWM1
  33.     Timer_A_generatePWM(TIMER_A0_BASE, &pwmConfig0_4);    //PWM2        
  34. }
复制代码
调用输出PWM波语句如下:
  1. pwmConfig0_4.dutyCycle =out_Duty_R;    //left
  2. pwmConfig0_3.dutyCycle =out_Duty_L;    //right
  3. GPIO_setOutputHighOnPin(GPIO_PORT_P1,GPIO_PIN6|GPIO_PIN7);//设置电机转动方向
  4. Timer_A_generatePWM(TIMER_A0_BASE, &pwmConfig0_4);//PWM1
  5. Timer_A_generatePWM(TIMER_A0_BASE, &pwmConfig0_3);//PWM2
复制代码
从官网PPT得知当电机转动方向关系表如下:
5.png

在我看来最难操作的就是电机闭环控制了。小车自带编码器的输出引脚分别连接在了定时器3的第0通道和第2通道。由于对这款开发板外设了解不多,不清楚MSP432是否支持正交解码功能,查阅相关资料并没发现该功能,也可能是我没找着。所以我在对反馈信息采集的时候用了定时器3输入捕获,读取单位时间脉冲数。但是就是这么一个简单的配置花了我足足一天的时间去查资料、尝试等等第二天终于能正确采集到左电机脉冲了。按理说右电机按照同样的方法配置不就好了吗?但是在我这里不是这样的,可能我没领悟精髓吧。对于msp432的中断控制我并不是那么清楚。查阅相关资料无功而返。在使用输入捕获时,我选择上升沿触发中断,并且记录单位时间中断发生的次数,间接知道了轮子转速。但是,编码器输出引脚都接在了定时器3上面的第0通道和第2通道,定时器3中断向量表如下:
6.png

每个定时器只有一个第0通道和N通道,那我就本能的将N通道理解成第1~6通道,但是当我实际操作的时候就不对了。第0通道采集回来的脉冲数正常,但是同样配置1~6中任何一个都不一样了,中断自动触发,并不是一个脉冲触发一次,哪怕设置中断优先级最高也不行。实在找不到原因。最后我才用跳线的方式,把定时器3的第2通道与定时器1的第0通道引脚短接,通过采集定时器2第0通道得到正确的脉冲数。这个方法并不建议大家使用,我也是迫于无聊奈,对MSP432TIMEA了解不够深入才这样的。
采集回来脉冲数接下来要做的就是知道轮子的正反转问题。若是有正交解码向NXP KERITISM4 K60芯片或者别的支持正交解码功能的芯片直接就可以测得正反转。但是对于MSP432好像并不支持这个功能。我采用了另一方发解决此问题。对于AB项编码器采集回来波形图如下:
7.png

对于A相(黄色)为下降沿时,B相(蓝色)为高电平,此时为正转反之为反转。就用此方法刻意得知小车轮子正反装状态。
输入捕获配置例程如下:
  1. Timer_A_CaptureModeConfig CaptureConfig1_0 =
  2. {
  3.         TIMER_A_CAPTURECOMPARE_REGISTER_0,      //通道     
  4.         TIMER_A_CAPTUREMODE_RISING_EDGE,       //上升沿触发中断
  5.         TIMER_A_CAPTURE_INPUTSELECT_CCIxA  ,                        
  6.         TIMER_A_CAPTURE_SYNCHRONOUS  ,   
  7.         TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE,//中断使能 //TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE ,      
  8.         TIMER_A_OUTPUTMODE_TOGGLE                             
  9. };
  10. /*captureModeconfig*/
  11. Timer_A_CaptureModeConfig CaptureConfig3_0 =
  12. {
  13.         TIMER_A_CAPTURECOMPARE_REGISTER_0,           
  14.         TIMER_A_CAPTUREMODE_RISING_EDGE,      
  15.         TIMER_A_CAPTURE_INPUTSELECT_CCIxA ,                        
  16.         TIMER_A_CAPTURE_SYNCHRONOUS  ,   
  17.         TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE,  //TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE ,      
  18.         TIMER_A_OUTPUTMODE_TOGGLE                             
  19. };
  20. /*captureModeconfig*/
  21. Timer_A_UpDownModeConfig upConfig =
  22. {
  23.         TIMER_A_CLOCKSOURCE_SMCLK,
  24.         TIMER_A_CLOCKSOURCE_DIVIDER_1 ,
  25.         6000,
  26.         TIMER_A_TAIE_INTERRUPT_DISABLE ,
  27.         TIMER_A_CCIE_CCR0_INTERRUPT_DISABLE ,
  28.         TIMER_A_DO_CLEAR
  29. };
  30. void TimaCaputerInit(void)
  31. {
  32.           GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P10, GPIO_PIN4, GPIO_PRIMARY_MODULE_FUNCTION);//P10.4
  33.           GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P8, GPIO_PIN0, GPIO_PRIMARY_MODULE_FUNCTION);//P8.2
  34.           Timer_A_initCapture(TIMER_A1_BASE,&CaptureConfig1_0);
  35.           Timer_A_initCapture(TIMER_A3_BASE,&CaptureConfig3_0);
  36.           Timer_A_configureUpDownMode(TIMER_A3_BASE,&upConfig);
  37.           Timer_A_configureUpDownMode(TIMER_A1_BASE,&upConfig);
  38.           //GPIO_setOutputHighOnPin(GPIO_PORT_P1,GPIO_PIN6|GPIO_PIN7);
  39.           //GPIO_setOutputHighOnPin  
  40.           Timer_A_enableCaptureCompareInterrupt(TIMER_A1_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_0);
  41.           Timer_A_enableCaptureCompareInterrupt(TIMER_A3_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_0);
  42.           Timer_A_enableInterrupt(TIMER_A1_BASE);
  43.           Timer_A_enableInterrupt(TIMER_A3_BASE);
  44.           Interrupt_enableInterrupt(INT_TA1_0);
  45.           Interrupt_enableInterrupt(INT_TA3_0);
  46.           Interrupt_enableMaster();
  47.           Timer_A_startCounter(TIMER_A3_BASE,TIMER_A_UP_MODE);
  48.           Timer_A_startCounter(TIMER_A1_BASE,TIMER_A_UP_MODE);        
  49. }
  50. void TA1_0_IRQHandler(void)
  51. {
  52.          Timer_A_clearCaptureCompareInterrupt(TIMER_A1_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_0);
  53.          if(Count_pulse_Left<100000)
  54.          {
  55.                Count_pulse_Left++;
  56.          }
  57.          else
  58.          {
  59.                Count_pulse_Left=0;
  60.                out_put_flag_left=1;
  61.          }         
  62. }
  63. void TA3_0_IRQHandler(void)
  64. {
  65.          Timer_A_clearCaptureCompareInterrupt(TIMER_A3_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_0);
  66.          if(Count_pulse_Right<100000)
  67.          {
  68.             Count_pulse_Right++;
  69.          }
  70.          else
  71.          {
  72.                  Count_pulse_Right=0;
  73.                  out_put_flag_right=1;
  74.          }
  75. }
复制代码
由以上基础,电机实现闭环控制。
第三部分:寻迹实现
轮子能转了之后接下啦就是控制轮子按照我们期望的方式转动即所谓的寻迹。小车上自带了红外和按键出发的传感器,可用于寻迹,但是本人使用的基于OpenMV的图像识别用于寻迹。OpenMV基于python编写。OpenMV学习连接如下:OpenMV学习
OpenMV 控制方法之抓取图片:
  1. import sensor#引入感光元件的模块
  2. # 设置**
  3. sensor.reset()#初始化感光元件
  4. sensor.set_pixformat(sensor.RGB565)#设置为彩色
  5. sensor.set_framesize(sensor.QVGA)#设置图像的大小
  6. sensor.skip_frames()#跳过n张照片,在更改设置后,跳过一些帧,等待感光元件变稳定。
  7. # 一直拍照
  8. while(True):
  9.     img = sensor.snapshot()#拍摄一张照片,img为一个image对象
复制代码
通过以上程序控制**抓取图片,对图片进行分析识别曲线。
OpemMV的曲线识别算法如下:
  1. import math,image,pyb,lcd
  2. from pyb import UART
  3. LINE_TH=600       #(阈值)该参数数值越大识别精度越高,但是漏判越多
  4. uart = UART(3, 9600)
  5. uart.init(9600, bits=8, parity=None, stop=1,timeout=5,timeout_char=5)
  6. lcd.init()
  7. led = pyb.LED(1)
  8. class Curve:
  9.     def findlines_return_K_B(self,img):
  10.         led = pyb.LED(2)
  11.         #global uart
  12.         #global LINE_TH
  13.         #最小二乘法相关参数
  14.         Arry_X=[]
  15.         Arry_Y=[]
  16.         ALL_X=0
  17.         ALL_Y=0
  18.         AVR_X=0
  19.         AVR_Y=0
  20.         ALL_X_Y=0
  21.         ALL_X_X=0
  22.         #返回的斜率截距与采集的斜率截距
  23.         K=0
  24.         B=0
  25.         K_LAST=0
  26.         B_LAST=0
  27.         #定义ROI变量
  28.         YY=110       #ROI最下面一块的起始行坐标  必须保证YY+HH=120
  29.         HH=10       #选取ROI区域高度,高度越高识别越准确
  30.         CH=5     #两块ROI区域交叉的像素点行数
  31.         #定义每段斜率的参数,一般来说参数统一
  32.         TH_MAR=50     #控制直线合并与RHO_MAR一起使用
  33.         RHO_MAR=50    #控制直线合并与TH_MAR一起使用
  34.         while True:
  35.             for l in img.find_lines((0,YY,160,HH),threshold = LINE_TH,theta_margin = TH_MAR, rho_margin = RHO_MAR):
  36.                 if  l.y2()!=l.y1() and l.x2()-l.x1()<50 and (60>l.theta()>0 or 179>l.theta()>120) :
  37.                     img.draw_line(l.line(), color = (255, 0, 0))
  38.                     Arry_Y.append((l.x2()+l.x1())/2)
  39.                     Arry_X.append((l.y2()+l.y1())/2)
  40.                     ALL_X=ALL_X+(l.y2()+l.y1())/2
  41.                     ALL_Y=ALL_Y+(l.x2()+l.x1())/2
  42.                     ALL_X_Y=ALL_X_Y+((l.y2()+l.y1())/2)*((l.x2()+l.x1())/2)
  43.                     ALL_X_X=ALL_X_X+((l.y2()+l.y1())/2)*((l.y2()+l.y1())/2)
  44.             YY=YY-CH        #YY表示纵坐标每次下移5行
  45.             if(YY+CH==0):
  46.                 break
  47.         #最小二乘法计算截距和谐率
  48.         if len(Arry_Y)>5:
  49.             AVR_X=ALL_X/len(Arry_Y)
  50.             AVR_Y=ALL_Y/len(Arry_Y)
  51.             K=(ALL_X_Y-len(Arry_Y)*AVR_X*AVR_Y)/(ALL_X_X-len(Arry_Y)*AVR_X*AVR_X)
  52.             B=AVR_Y-K*AVR_X
  53.             #print(K,B)
  54.             #显示所用的起止点
  55.             X_0=int(B)
  56.             X_1=int(K*119+B)
  57.             #显示斜率和解决,并以此划出一条拟合后的直线
  58.             img.draw_string(30,90,'K='+str(K),color=(0,0,0),mono_space=False,x_spacing=1)
  59.             img.draw_string(30,100,'B='+str(B),color=(0,0,0),mono_space=False,x_spacing=1)
  60.             img.draw_line((X_0,0,X_1,119), color=(0,0,128))
  61.         led.on()
  62.         lcd.display(img)
  63.         K=int(K*100)  
  64.         B=int(B)
复制代码
以上程序,为了直观的描述偏差与斜率,显示了寻线得到的拟合直线(下图蓝色线)。
10.png

9.png

8.png

寻线完成之后,将计算完成的相应的偏差K,B通过串口模块传递给MSP432芯片。
OpenMV的P4,P5引脚是串口的Rx脚和Tx脚。
对于小车头的触角开关,应交为:P4端口的5,6,7,3,2,0引脚。GPIO获取引脚状态。开关触发中断例程如下:
  1. GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN6|GPIO_PIN7|GPIO_PIN5|GPIO_PIN3|GPIO_PIN2|GPIO_PIN0);
  2. GPIO_clearInterruptFlag(GPIO_PORT_P4, GPIO_PIN6|GPIO_PIN7|GPIO_PIN5|GPIO_PIN3|GPIO_PIN2|GPIO_PIN0);        
  3. GPIO_enableInterrupt(GPIO_PORT_P4, GPIO_PIN6|GPIO_PIN7|GPIO_PIN5|GPIO_PIN3|GPIO_PIN2|GPIO_PIN0);
  4. Interrupt_enableInterrupt(INT_PORT4);
  5. void PORT4_IRQHandler(void)
  6. {
  7.                 Down_Flag7=0,Down_Flag6=0,Down_Flag5=0,Down_Flag4=0,Down_Flag2=0,Down_Flag0=0;
  8.                 volatile uint32_t  state ;
  9.                 state  = GPIO_getEnabledInterruptStatus(GPIO_PORT_P4);//»ñȡ״̬
  10.                
  11.                 if(state &GPIO_PIN7 ){  
  12.                 if(!(GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN7))) //È·ÈÏ°´ÏÂ
  13.                 {
  14.                         Down_Flag_Arry[0]=1;
  15.                         GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN7 );//ÊÍ·Å°´¼üºóÀ­¸ß
  16.                 }
  17.                 }
  18.                
  19.                 if(state &GPIO_PIN6 ){
  20.                 if(!(GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN6))) //È·ÈÏ°´ÏÂ
  21.                 {
  22.                         Down_Flag_Arry[1]=1;
  23.                         GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN6 );
  24.                 }
  25.                 }               
  26.                 if(state &GPIO_PIN5 ){                 
  27.                 if(!(GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN5))) //È·ÈÏ°´ÏÂ
  28.                 {
  29.                         Down_Flag_Arry[2]=1;
  30.                         GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN5 );
  31.                 }
  32.                 }               
  33.                 if(state &GPIO_PIN3 ){
  34.                 if(!(GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN3))) //È·ÈÏ°´ÏÂ
  35.                 {
  36.                         Down_Flag_Arry[3]=1;
  37.                         GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN3 );
  38.                 }
  39.                 }
  40.                 if(state &GPIO_PIN2 ){
  41.                 if(!(GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN2))) //È·ÈÏ°´ÏÂ
  42.                 {
  43.                         Down_Flag_Arry[4]=1;
  44.                         GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN2 );
  45.                 }
  46.                 }               
  47.                 if(state &GPIO_PIN0 ){
  48.                 if(!(GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0)))
  49.                 {
  50.                         Down_Flag_Arry[5]=1;
  51.                         GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4, GPIO_PIN0 );
  52.                 }
  53.                 }               
  54.                 GPIO_clearInterruptFlag(GPIO_PORT_P4, GPIO_PIN6 );
  55. }
复制代码
第四部分:人机交互
最后一部分则是人机交互部分,用显示屏显示相关信息或者变量信息方便调试。我用的是0.96寸OLED 屏幕,至于程序源代码其实网上有很多,我用的是模拟SPI通讯,所以对引脚几乎没限制。这里带代码就不啰嗦了,自行去网上寻找一个模拟SPI通讯的显示屏修改GPIO控制方法就好。

11.png

第五部分:总结
综上所述:对于MSP EXP432P401R LaunchPad的使用,其实本人还有许多不清楚的地方,在学习过程中,去图书馆借了两本书刘杰所著《基于固件库的MSP432为控制其原理及其应用》和北京航天航空大学出版社出版的《MSP432系列超低功耗ARM Cotex_M4微处理器原理与实践》,建议大街可以看看这两本书。由于MSP432在国内资源相对较少你,所以与之类似的教程也较少。欢迎大家探讨交流。





打赏

参与人数 1赫兹币 +2 收起 理由
EPT_robot + 2 很给力!

查看全部打赏

30

主题

49

帖子

179

积分

二氧化硅

Rank: 2

积分
179
发表于 2019-3-6 13:02:06   只看该作者
可以说是非常的详细了 谢谢楼主的分享

1

主题

12

帖子

149

积分

二氧化硅

Rank: 2

积分
149
发表于 2019-3-17 15:12:42   只看该作者
很厉害,感谢楼主的分享
快速回复 返回顶部 返回列表