STM32F407提供了工作频率为168 MHz的Cortex™-M4内核(具有浮点单元)的性能。我的32是正点原子的最小系统板,它大部分的引脚和探索者是一样的。
串口1:PA9/10复用为串口1用于和OPENMV通信。
PWM:PA6/7复用为定时器3用于PWM输出。
IN1–4: PF2/3/6/7作为电机输出。
主要程序就是串口接收处理程序,也就是和OPENMV的通信问题。下面开始讲解一下代码。
#include "sys.h"
#include "usart.h"
EN_USART1_RX //如果使能了接收
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
u16 USART_RX_STA=0; //接收状态标记
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}
这里的0X0A和0X0D就是我OPENMV端发送的\r\n,分别是回车换行,对应的ASCII是0X0D和0X0A。所以这两个数为我们的接收完成的一个检测标志。
#include "dispose.h"
#include "sys.h"
#include "usart.h"
#include "led.h"
double q,x,m,nb,mb,z;
int j,i,c1,w1,v1;
u8 v=0;
void dispose_init(void) //数据接收处理函数
{
m=0.0;
v=0;
j=0;
q=0.0;
if(USART_RX_STA&0x8000)
{
x=0.0; //接收新的数X置0
if(USART_RX_BUF[0]=='x') //如果接收到的是X
{ v++;
while(USART_RX_BUF[v]!='z')
{
if(USART_RX_BUF[1]!='-') //是正数的情况
{
c1=0;
if(USART_RX_BUF[3]=='.') //如果是十位数
{
if(USART_RX_BUF[v]=='.')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q+1);
nb=USART_RX_BUF[v]-48; //ASCii码减去48是对应的数字
x+=nb*m;
q++;
v++;
}
if(USART_RX_BUF[2]=='.') //如果是个位数和小数
{
if(USART_RX_BUF[v]=='.')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q);
nb=USART_RX_BUF[v]-48;
x+=nb*m;
q++;
v++;
}
}
else if(USART_RX_BUF[1]=='-') //如果是负数
{
c1=1;
if(USART_RX_BUF[4]=='.') //如果是负十位数
{
if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q+1);
nb=USART_RX_BUF[v]-48;
x+=nb*m;
q++;
v++;
}
if(USART_RX_BUF[3]=='.') //如果是负个位数和小数
{
if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q);
nb=USART_RX_BUF[v]-48;
x+=nb*m;
q++;
v++;
}
}
}
}
if(USART_RX_BUF[v]=='z') //当接收到Z的时候
{
q=0.0;
z=0.0;
v++;
v1=v;
while(USART_RX_BUF[v]!='e') //我设置的openmv中发送的格式是以e结尾的
{
if(USART_RX_BUF[v1]!='-') //是正数的情况
{
w1=0;
if(USART_RX_BUF[v1+2]=='.') //如果是十位数
{
if(USART_RX_BUF[v]=='.')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q+1);
mb=USART_RX_BUF[v]-48;
z+=mb*m;
v++;
q++;
}
if(USART_RX_BUF[v1+1]=='.') //如果是个位数和小数
{
if(USART_RX_BUF[v]=='.')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q);
mb=USART_RX_BUF[v]-48;
z+=mb*m;
v++;
q++;
}
}
else if(USART_RX_BUF[v1]=='-') //如果是负数
{
w1=1;
if(USART_RX_BUF[v1+3]=='.') //如果是负十位数
{
if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q+1);
mb=USART_RX_BUF[v]-48;
z+=mb*m;
q++;
v++;
}
if(USART_RX_BUF[v1+2]=='.') //如果是负个位数和小数
{
if(USART_RX_BUF[v]=='.'||USART_RX_BUF[v]=='-')
v++;
if(USART_RX_BUF[v]!='.')
m=pow(10.0,-q);
mb=USART_RX_BUF[v]-48;
z+=mb*m;
q++;
v++;
}
}
}
}
}
USART_RX_STA=0; //清空接收的数组
}
这里的串口处理函数我写的比较长,重复的地方比较多,需要改进。其实可以简单一些的,我这里写的是可以接收到两位小数的数据,一种更简单的方法的话就是在OPENMV端把小数扩大为整数并传输,这样传输的话就简单很多。
我的格式是(“x%.2fz%.2fe”% print_args+’\r\n’),打个比方就是x-1.22z-6.78e+回车换行。我的思路是通过把字母后的数字加起来存放在mb,nb中,字母和回车换行就被剔除了。
#include "pwm.h"
#include "sys.h"
void pwm_Init(u32 arr,u32 psc)
{
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定时器结构体初始化
TIM_OCInitTypeDef TIM_OCInitStructure; //
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能定时器14的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOC时钟
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_TIM3); //GPIOF6复用为定时器
GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM3); //GPIOF7复用为定时器
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF; //IO口复用模式
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6|GPIO_Pin_7; //设置为管脚6,7
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP; //上拉
GPIO_InitStructure.GPIO_Speed =GPIO_High_Speed; //100Ms
GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIO初始化
TIM_TimeBaseInitStructure.TIM_Prescaler =psc; //预分频系数
TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_Period =arr; //自动装载值
TIM_TimeBaseInitStructure.TIM_ClockDivision =TIM_CKD_DIV1; //
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); //初始化定时器3
//初始化TIM8 Channe11 PWM模式
TIM_OCInitStructure.TIM_OCMode =TIM_OCMode_PWM1; //选择定时器为PWM1,(TIM脉冲宽度调制模式2)
TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity =TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
// TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC1Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3OC1
TIM_OC2Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3OC2
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIM3在CCR1上的预装载寄存器
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_ARRPreloadConfig(TIM3, ENABLE); //使能APRE(arr)
TIM_Cmd( TIM3,ENABLE);
TIM_CtrlPWMOutputs(TIM3, ENABLE);
}
PWM呢就是用定时器3来实现,这里应该没什么问题。PWM输出要注意占空比和PWM频率。这里简单介绍一下PWM。首先,确定TIMx的时钟,除非APB1的时钟分频数设置为1,否则通用定时器TIMx的时钟是APB1时钟的2倍,这时的TIMx时钟为72MHz,用这个TIMx时钟72MHz除以(PSC+1),再把这个值乘以(ARR+1)得出PWM频率。如果想调整PWM占空比精度,则只需降低PSC寄存器的值即可。还有一点要注意的是PWM频率太慢,车轮会有卡卡停停的现象。 频率太高,小车则动不了。
#include "en.h"
void En_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_6|GPIO_Pin_3|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOF,GPIO_Pin_7|GPIO_Pin_6|GPIO_Pin_3|GPIO_Pin_2);//GPIOF9,F10设置高
}
也就是IN1–4的初始化
void car_mode(void)
{
if(x>1.1&&c1==0&&z<14)//左转c1标志正数
{
IN1=0;
IN2=1;
IN3=0;
IN4=1;
TIM_SetCompare1(TIM3,3600);
TIM_SetCompare2(TIM3,3600); //修改比较值(ccr),改变占空比
delay_ms(200);
LED0=!LED0;
x=0.0;
LED0=0;
}
else if(x>1.1&&c1==1&&z<14) //右转
{
IN1=1;
IN2=0;
IN3=1;
IN4=0;
TIM_SetCompare1(TIM3,3600);
TIM_SetCompare2(TIM3,3600); //修改比较值(ccr),改变占空比
delay_ms(200);
LED0=!LED0;
x=0.0;
c1=0;
LED0=0;
}
else if(z>14.5&&w1==1&&x<2.3) //直走
{
IN1=0;
IN2=1;
IN3=1;
IN4=0;
TIM_SetCompare1(TIM3,3600); //修改比较值(ccr),改变占空比
TIM_SetCompare2(TIM3,3600); //修改比较值(ccr),改变占空比
delay_ms(250);
w1=0; //w1置0
LED0=!LED0;
z=0.0;
LED0=0;
}
else if(z<5&&w1==1&&x<1.2) //后退
{
IN1=1;
IN2=0;
IN3=0;
IN4=1;
TIM_SetCompare1(TIM3,3600); //修改比较值(ccr),改变占空比
TIM_SetCompare2(TIM3,3600); //修改比较值(ccr),改变占空比
LED0=!LED0;
delay_ms(200);
z=0.0;
w1=0; //w1置0
LED0=0;
}
else if(z==0&&x==0) //停
{
z=0.0;
x=0.0;
IN1=0;
IN2=0;
IN3=0;
IN4=0;
LED1=!LED1;
delay_ms(500);
}
}
/*
void car_pid()
{
last_x_error=x_error;
x_error=x-1;
ut=x_P*x_error-x_D*(last_x_error-x_error);
L_Value=ut*2000;
R_Value=ut*2000;
L_Value=L_Value<1000?L_Value:1200;
R_Value=R_Value<1000?R_Value:1200;
}
*/
这是简单的驱动,没有加入PID,不过也还算比较精确,慢慢调一下PWM的占空比和频率就可以了。PID算法我打算回学校之后拿个有编码器的电机还有舵机再做出来,这样可以实现比较精确的控制了。
说一下下一个项目的想法。通过树莓派运行ROS实现雷达导航以及制图,同时还有语音识别功能,跟随功能,算是这量小车的升级版吧!加油!预计3个月完成,需要学习树莓派,LINUX,Py。等做好后,继续写博客 。