一楼做目录
2楼: 一、智能车的组装
3楼:二、用NUCLEO或者Discovery板上带的st-link调试小车
4楼:三、程序目录说明
6楼:五、电机驱动控制分析
10楼:七、电机编码器的数据获取
11楼:八、蓝牙通讯实验
12楼:九、基于模型的两轮平衡车设计
13楼:十、两轮平衡车基于模型的设计流程
14楼:十一、基于模型的两轮车典型案例
15楼:十二、两轮平衡车的建模
18楼:十五、走8字算法仿真
19楼:十六、自动代码生成
一楼做目录
2楼: 一、智能车的组装
3楼:二、用NUCLEO或者Discovery板上带的st-link调试小车
4楼:三、程序目录说明
6楼:五、电机驱动控制分析
10楼:七、电机编码器的数据获取
11楼:八、蓝牙通讯实验
12楼:九、基于模型的两轮平衡车设计
13楼:十、两轮平衡车基于模型的设计流程
14楼:十一、基于模型的两轮车典型案例
15楼:十二、两轮平衡车的建模
18楼:十五、走8字算法仿真
19楼:十六、自动代码生成
一、智能车的组装
上周就收到包裹了,可惜没在单位,今天终于回来了
拆开包装发现每个零件都包的很严实,还是不错的。
打开每个零件之后感觉东西没有想的那么多啊,主要是都集成在电路板上了。
安装过程还是很简单的,将电池夹在电路板和车轮中间,用铜柱固定就好了。不过要主意电池的安装方向,避免不好接线。调试的时候可以考虑把亚克力板拿掉方便调试。
但是电机不像是新的,这点不太满意。
上电之后按一下按键,小车就可以立起来了,但是感觉不太平稳,需要再调整。
小车视频:
视频地址:http://player.youku.com/player.php/sid/XOTYzODA5NzM2/v.swf
二、用NUCLEO或者Discovery板上带的st-link调试小车
因为手里有nucleo和discovery板,上面已经集成了stlink v2,所以可以用来调试小车,而不需要额外再买下载器。
首先看nucleo的文档,断开写有st-link/nucleo的两个跳帽(下图cn2),此时nucleo就可以用来调试其他板子了。
这里我们只需用cn4的第二(SWCLK)、第三(GND)、第四(SWDIO)根线。
我们在看小车的原理图:
小车的stlink为J7,分别为PA13(SWDIO,板子正面写的IO),GND,PA14(SWCLK,板子正面的SCK)
将这三根线与nucleo进行连接:
就可以进行调试了。
在mdk中打开程序,点击options for target:
然后选Debug,ST-Link Debugger,Settings:(记得把前边的use点上)
就可以看到已经连接上板子了(SWDIO中有IDCODE和Name):
之后就可以进行调试并下载程序了。
三、程序目录说明(个人看法,欢迎大家讨论提问)
选择的资料文件是“Mini Balance V2.5 标准版源码(集成DMP 卡尔曼滤波 互补滤波)”
文件目录说明:
打开Project--STM32F10x_StdPeriph_Template--MDK-ARM--Project.uvproj就是项目的工程了。
打开工程,看左边的文件列表,这里的文件夹并不是和目录里的文件夹一一对应的(所以说比较乱,要是我们自己建工程的话,尽量做成一一对应的)。
工程目录说明:
User:main程序,也就是程序的入口点主程序;stm32f10x_it.c:写中断程序的文件,这里中断函数都是空,应该是分别写在了drivers中的各个文件中;实际上目录下还有stm32f10x_conf.h文件,这是用来筛选包含的头文件的,我们打开它可以看到:
就是将需要使用的头文件前边的注释符去掉;这个程序用到了exit(外部中断),gpio(io口),rcc(时钟),usart(串口),misc(中断)。
StdPeriph_Driver:st官方标准库,用到哪个就包含哪个,具体的设置在上边提到的stm32f10x_conf.h文件中,这里放的是从官方库中复制过来具体文件;
CMSIS:ARM Cortex™ 微控制器软件接口标准,是arm内核的通用程序,具体可以百度。
MDK-ARM:其实应该命名为Startup文件夹,因为放的是启动时最先加载的汇编文件,小车的芯片应该用的是其中的startup_stm32f10x_md.s;
Drivers:用户驱动,就是自己定义的关于led、串口、定时器等的驱动函数;
Mini-Balance:放的应该是滤波,oled显示,电机控制函数,也是小车能平衡和移动的重点。
另:点击keil上的这个按钮:
可以看到添加到工程的文件(自己添加时也是这么添加)
四、陀螺仪MPU6050简单分析
首先,网上关于mpu6050的帖子还是很多的,比如这篇加速度计和陀螺仪指南 ,详细的讲解了加速度计和陀螺仪的知识。
板上的mpu6050是通过I2C的方式与芯片进行连接的,关于i2c的介绍网上有很多,stm32芯片中集成了硬件i2c,在官方驱动库中也可以直接使用。看了一下示例程序,好像都是用位绑定和寄存器开发的程序,感觉不太容易看得懂,所以分享一下我用库函数写的简单的mpu6050的程序。(注:我这个工程里只是用了MPU6050的数据采集,没有其他功能)
首先,要使用st官方的i2c库,需要在stm32f10x_conf.h中将#include "stm32f10x_i2c.h"一行的注释取消:
在user目录下建立了mpu6050.c和mpu6050.h,
其中头文件内容为,地址可以通过查数据手册得到:
#ifndef __MPU6050_H #define __MPU6050_H #include "stm32f10x.h" /* MPU6050 Register Address ------------------------------------------------------------*/ #define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz) #define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz) #define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s) #define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz) #define ACCEL_XOUT_H 0x3B #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 #define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 #define GYRO_ZOUT_L 0x48 #define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用) #define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读) #define SlaveAddress 0xD0 //IIC写入时的地址字节数据 void MPU6050_Init(void); void InitMPU6050(void); unsigned int GetData(unsigned char REG_Address); #endif
在mpu6050.c中添加需要使用的函数,包括:
外部调用的:
void MPU6050_Init(void);//调用I2C_GPIO_Config()和I2C_Mode_Config()进行初始化
void InitMPU6050(void);//写入初始化命令
unsigned int GetData(unsigned char REG_Address);//获得数据
内部调用的:
void I2C_GPIO_Config(void)
void I2C_Mode_Config(void)
void I2C_ByteWrite(uint8_t REG_Address,uint8_t REG_data)//写一个字节
uint8_t I2C_ByteRead(uint8_t REG_Address)//读一个字节
具体函数请参见工程,主要就是配置I2C所使用的GPIO口和I2C本身的配置,然后调用I2C的官方库函数进行控制命令的写入和数据的接收。
主函数中,将获得的数据通过串口发送出来(串口是用的现成的驱动,包含了usart1.c和usart1.h):
int main(void) { /* Add your application code here */ USART1_Config(); MPU6050_Init(); InitMPU6050(); /* Infinite loop */ while (1) { printf("\r\n---------加速度X轴原始数据---------%d \r\n",GetData(ACCEL_XOUT_H)); printf("\r\n---------加速度Y轴原始数据---------%d \r\n",GetData(ACCEL_YOUT_H)); printf("\r\n---------加速度Z轴原始数据---------%d \r\n",GetData(ACCEL_ZOUT_H)); printf("\r\n---------陀螺仪X轴原始数据---------%d \r\n",GetData(GYRO_XOUT_H)); printf("\r\n---------陀螺仪Y轴原始数据---------%d \r\n",GetData(GYRO_YOUT_H)); printf("\r\n---------陀螺仪Z轴原始数据---------%d \r\n",GetData(GYRO_ZOUT_H)); delay_ms(1000); } }
五、电机驱动控制分析
直流电机是利用通电线圈在磁场中受到磁场力矩的作用后会发生转动的原理。通常直径越大,电机扭力也越强。由于直流电极转速高、扭力小,用于控制时通常增加减速装置,就是小车用的减速电机。
通常用的直流电机驱动电路是H桥电路,一般包括4个场效应管和一个电机。如图。要使电机转动,必须导通对角线上的一对场效应管,根据不同的导通情况,电流可能会从左至右或从右至左流过电机,从而实现电机不同方向的转动。
电流方向只能实现电机的正转和反转,但不能实现电机的速度控制,这时就需要采用PWM技术(脉宽调制),是利用处理器的数字输出来控制模拟电路的一种技术,它实质上是把恒定的直流电源电压调制成频率一定,宽度可变的脉冲电压序列,从而改变平均输出电压的大小,达到控制电机转速的目的。
小车的驱动芯片采用的是TB6612FNG芯片,具有大电流MOSFET-H桥结构,双通道电路输出,可同时驱动2个电机。
根据原理图,电机分别由PB1(PWMA),PB14(AIN1),PB15(AIN2)和PB0(PWMB),PB12(BIN2),PB13(BIN1)来控制,其他引脚已接好。按照下图真值表可以进行控制的设计。
经过以上说明,其实电机的控制并不难,就是需要产生合适的PWM输出进行控制。
下一贴将学习stm32芯片的PWM的产生方式~
六、使用STM32定时器产生PWM输出
以前也不太会使用定时器,正好借这个机会学习一下~
stm32芯片一共有8个16位定时器:TIM6和TIM7是基本定时器;TIM2,3,4,5是通用定时器;TIM1,8是高级定时器;定时器具有定时、信号频率测量、PWM输出、步进电机、编码器接口等功能。
它们的区别是:
基本定时器:只具有基本功能,即累加的时钟脉冲超过预定值时,触发中断或者DMA请求,可以作为其他通用定时器的始 终基准;时钟源是TIMxCLK,时钟源经过PSC分频输入至TIMx_CNT,基本定时器只能工作在向上计数模式,在TIMx_ARR中保存溢出值,工作时, 脉冲计数器 TIMx_CNT由时钟触发进行计数,当其值达到TIMx_ARR中的值时,产生溢出,触发中断,然后TIMx_CNT清零,重新向上计数
通用定时器:除了基本定时期的功能,还具有测量输入脉冲频率、脉宽,输出PWM,编码器接口等功能。它相比于基本定时器,多出了一个捕获/比较寄存器TIMx_CCR,在输入时捕获脉冲在电平翻转时TIMx_CNT中的值,实现频率测量;在输出时可以存储一个值,把这个值与TIMx_CNT中的值比较,从而输出不同的高低电平;
(根据新版的程序,应该使用TIM3的CH3和CH4产生的PWM输出)
PWM输出过程:TIMx_CCR被用作比较寄存器;若TIMx_CNT被配置为向上记数模式,值为X,将TIMx_ARR的值配置为N,则TIMx_CNT的X值不断累加,达到N时会置0并重新计数;计数的同时,TIMx_CNT中的值会与TIMx_CCR的值A进行比较,X小于A时输出高电平(或低),X大于A时输出低电平(或高),如此循环,就可以得到周期由TIMx_ARR的值所控制的(N+1乘以时钟周期),脉冲宽度由A所控制的,占空比为A/(N+1)的PWM输出。
高级定时器:除了有基本、通用定时器的所有功能,还有三相6步点击接口,PWM驱动电路的死区时间控制等功能。
要配置PWM输出,需要一下2个步骤:
1设置GPIO口
2设置定时器TIM3
在版主发的程序中设置程序在motor.c文件中,采用寄存器操作(可惜不会。。。),有详细的注释,可以看一看;我之前都是用库函数写的,所以再用库函数写一个试试吧。
电机接口应该是GPIOB12,13,14,15,输出用的是TIM3的CH4和CH3;
先配置GPIO口:
void MiniBalance_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); //使能端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;//PB0和PB1的默认复用功能对应CH3和CH4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
设置完端口就可以设置定时器模式,产生PWM:
void MiniBalance_PWM_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//时基初始化结构体
TIM_OCInitTypeDef TIM_OCInitStructure;//模式配置结构体
TIM_TimeBaseStructure.TIM_Period =aar; //定时周期,存储到TIMx_ARR的值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //预分频的值,对TIMxCLK分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
另外:库函数中
void TIM_SetCompare3 | ( | TIM_TypeDef * | TIMx, |
uint16_t | Compare3 | ||
) |
七、电机编码器的数据获取
电机的编码器其实就是一种传感器,可以把旋转位移转换成一串数字脉冲信号,MCU可以读取这段脉冲信号通过计数来获取电机的转速。
小车的原理图中显示编码器的接口分别为PA0和PA1,PB6和PB7:
查询datasheet:
PA0和PA1对应着TIM2,PB6和PB7对应着TIM4.所以系统应该采用定时器2和4分别对编码器结果进行计数。
参考数据手册中定时器用作编码器接口模式的内容:选择编码器接口模式的方法是:如果计数器只在TI2的边沿计数,则置TIMx_SMCR寄存器中的SMS=001;如果只在TI1边沿计数,则置SMS=010;如果计数器同时在TI1和TI2边沿计数,则置SMS=011。 通过设置TIMx_CCER寄存器中的CC1P和CC2P位,可以选择TI1和TI2极性;如果需要,还可以对输入滤波器编程。 两个输入TI1和TI2被用来作为增量编码器的接口。参看表77,假定计数器已经启动(TIMx_CR1寄存器中的CEN=’1’),计数器由每次在TI1FP1或TI2FP2上的有效跳变驱动。TI1FP1和TI2FP2是TI1和TI2在通过输入滤波器和极性控制后的信号;如果没有滤波和变相,则TI1FP1=TI1,TI2FP2=TI2。根据两个输入信号的跳变顺序,产生了计数脉冲和方向信号。依据两个输入信号的跳变顺序,计数器向上或向下计数,同时硬件对TIMx_CR1寄存器的DIR位进行相应的设置。不管计数器是依靠TI1计数、依靠TI2计数或者同时依靠TI1和TI2计数。在任一输入端(TI1或者TI2)的跳变都会重新计算DIR位。 编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。所以在开始计数之前必须配置TIMx_ARR;同样,捕获器、比较器、预分频器、触发输出特性等仍工作如常。
可以看出需要配置定时器的CCMR,CCER,SMCR和CR1寄存器。定时器2的配置为:
void Encoder_Init_TIM2(void) { RCC->APB1ENR|=1<<0; RCC->APB2ENR|=1<<2; GPIOA->CRL&=0XFFFFFF00;//PA0 PA1 GPIOA->CRL|=0X00000044; TIM2->PSC = 0x0; TIM2->ARR = ENCODER_TIM_PERIOD-1; TIM2->CCMR1 |= 1<<0; TIM2->CCMR1 |= 1<<8; TIM2->CCER |= 0<<1; TIM2->CCER |= 0<<5; TIM2->SMCR |= 3<<0; //SMS='011' TIM2->CR1 |= 0x01; }
最后,读取编码器数据的函数按照示例为:
int Read_Encoder(u8 TIMX) { int Encoder_TIM; switch(TIMX) { case 2: Encoder_TIM= (short)TIM2 -> CNT; TIM2 -> CNT=0;break; case 3: Encoder_TIM= (short)TIM3 -> CNT; TIM3 -> CNT=0;break; case 4: Encoder_TIM= (short)TIM4 -> CNT; TIM4 -> CNT=0;break; default: Encoder_TIM=0; } return Encoder_TIM; }
从CNT寄存器中读取数据。
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |
打赏帖 | |
---|---|
【换取逻辑分析仪】-基于ADI单片机MAX78000的简易MP3音乐播放器被打赏48分 | |
我想要一部加热台+树莓派PICO驱动AHT10被打赏38分 | |
【换取逻辑分析仪】-硬件SPI驱动OLED屏幕被打赏36分 | |
换逻辑分析仪+上下拉与多路选择器被打赏29分 | |
Let'sdo第3期任务合集被打赏50分 | |
换逻辑分析仪+Verilog三态门被打赏27分 | |
换逻辑分析仪+Verilog多输出门被打赏24分 | |
【分享评测,赢取加热台】使用8051单片机驱动WS2812被打赏40分 | |
【换取逻辑分析仪】rtthread添加RRH62000传感器驱动-基于野火启明6M5被打赏48分 | |
换逻辑分析仪+Verilog多输入门被打赏27分 |