title: 蓝牙小车开发时的一些细节
cover: >-
https://tse1-mm.cn.bing.net/th/id/OIP-C.BrSgB91U1MPHGyaaZEqcbwHaEo?w=273&h=180&c=7&r=0&o=5&dpr=1.3&pid=1.7
abbrlink: 842d5faf
date:
tags:
#小车基本运动之最重要的—PWM
##1.PWM(Pulse Width Modulation)脉冲宽度调制是什么?
为何这个PWM(脉冲宽度)如此重要呢?因为在具有惯性的系统中,我们可以通过对一系列脉冲的宽度进行调制,来等效地获得我们所需要的模拟量,经常用于电机控速等领域(属于是常客了)
举一个例子:比如说我的占空比为50%,那么在这个一个PWM的周期内,电机处于高电平的时间是只有周期的一半,低电平默认为0,那么我们计算等效电压—( T(on) * 5v + T(off) * 0v ) / Ts = 等效电压V 所以50%占空比可以等效为2.5v电压
通过这个等效电压的例子,也为我们如何控制电机的速度以及呼吸灯等等一系列工业生产提供了新的思路—通过PWM(即控制占空比)来控制等效电压—从而GPIO配置为复用推挽输出,定时器的四个通道(STM32外设)来控制引脚输出
##2.如何实现PWM?
实现PWM,我们需要用到定时器和OC(输出比较),通过定时器不断计数然后和RCC(参考比较值)不断比较,当计数小于RCC时,输出的电平为高电平,而当计数大于RCC时,输出的电平为低电平—这个过程叫输出比较—然后统计高电平在总的计数期间的比值—占空比。
请看此图
我们三步走战略,1.初始化时基单元,2.GPIO串口复用AFIO初始化 3.定时器初始化。 以及知道参数计算的公式:1. PWMFreq = CK_PSC / (PSC+1) / (ARR+1)
2.PWM占空比Duty = CCR / (ARR + 1)
3.PWM分辨率Reso = 1 / (ARR + 1)
##3.PWM代码实现
放在***\Hardware中**那么请看具体代码
这是PWM.h的具体代码
#ifndef __PWM_H__
#define __PWM_H__
void PWM_Init(void);
#endif
这是PWM.c的具体代码
#include "stm32f10x.h" // Device header
//1.时基单元
//2.oc输出比较
//3.GPIO初始化
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //开启TIM4的外部时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//PB6-PB9 开启GPIOB的外部时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //配置为复用推挽输出,定时器的四个通道来控制引脚输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//定义时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //从0开始向上计数
TIM_TimeBaseInitStruct.TIM_Period = 100 - 1; //ARR重装值
TIM_TimeBaseInitStruct.TIM_Prescaler = 36 - 1; //PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //高级定时器才有的,我们用不到这里
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //预分频 DIV1是0预分频
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct);
// OC 输出比较 初始化OC比较的属性
TIM_OCInitTypeDef TIM_OCInitStruct;
//OC1 输出比较通道口1
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; //以高电平为有效电平
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0; //CCR 预期值 CNT 与 CCR进行比较
TIM_OC1Init(TIM4,&TIM_OCInitStruct);
//oc2
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; //以高电平为有效电平
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0; //CCR 预期值 CNT 与 CCR进行比较
TIM_OC2Init(TIM4,&TIM_OCInitStruct);
//oc3
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; //以高电平为有效电平
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0; //CCR 预期值 CNT 与 CCR进行比较
TIM_OC3Init(TIM4,&TIM_OCInitStruct);
//oc4
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; //以高电平为有效电平
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0; //CCR 预期值 CNT 与 CCR进行比较
TIM_OC4Init(TIM4,&TIM_OCInitStruct);
//在需要不断切换定时器的周期时,而且周期都比较短, T = 1 / F
//程序员需要通过预加载寄存器配合自动重装载寄存器,来操作定时器 缓存
TIM_OC1PreloadConfig(TIM4,TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM4,TIM_OCPreload_Enable);
TIM_OC3PreloadConfig(TIM4,TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM4, ENABLE);
TIM_Cmd(TIM4,ENABLE);
}
我们来设置小车的移动和速度。
我们分别将它们命名为Motor.c,Motor.h并放到\Hardware文件中
此为Motor.h
#ifndef __MOTOR_H__
#define __MOTOR_H__
void Motor_Init(void);
void Motor_SetSpeed(uint8_t left_1,uint8_t left_2,uint8_t right_1,uint8_t right_2);
void Motor_Run(uint8_t Speed,uint16_t time);
void Motor_Back(uint8_t Speed,uint16_t time);
void Motor_TurnLeft(uint8_t Speed,uint16_t time);
void Motor_Spin_Left(uint8_t Speed,uint16_t time);
void Motor_TurnRight(uint8_t Speed,uint16_t time);
void Motor_Spin_Right(uint8_t Speed,uint16_t time);
void Motor_Brake(uint16_t time);
#endif
此为Motor.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
#include "Delay.h"
//机器人初始化
void Motor_Init(void)
{
PWM_Init();
}
//控制小车轮子的速度,分别设置四个通道的RCC,每两个通道,控制一个轮子
void Motor_SetSpeed(uint8_t left_1,uint8_t left_2,uint8_t right_1,uint8_t right_2)
{
TIM_SetCompare1(TIM4,left_1); //TIM_SetCompare是为了改变我们设置在时基单元里的RCC的大小
TIM_SetCompare2(TIM4,left_2);
TIM_SetCompare3(TIM4,right_1);
TIM_SetCompare4(TIM4,right_2);
}
//车子向前开动
void Motor_Run(uint8_t Speed,uint16_t time)
{
if (Speed > 100)
{
Speed = 100;
}
else if (Speed < 0)
{
Speed = 0;
}
Motor_SetSpeed(Speed,0,Speed,0); //可以从输出比较的图看出来
Delay_ms(time);
Motor_SetSpeed(0,0,0,0); //最后车子停止运动
}
//车子后退
void Motor_Back(uint8_t Speed,uint16_t time)
{
if (Speed > 100)
{
Speed = 100;
}
else if (Speed < 0)
{
Speed = 0;
}
Motor_SetSpeed(0,Speed,0,Speed); //可以从输出比较的图看出来
Delay_ms(time);
Motor_SetSpeed(0,0,0,0); //最后车子停止运动
}
//车子左转
void Motor_TurnLeft(uint8_t Speed,uint16_t time)
{
if (Speed > 100)
{
Speed = 100;
}
else if (Speed < 0)
{
Speed = 0;
}
Motor_SetSpeed(0,0,Speed,0); //可以从输出比较的图看出来
Delay_ms(time);
Motor_SetSpeed(0,0,0,0); //最后车子停止运动
}
//小车左旋转
void Motor_Spin_Left(uint8_t Speed,uint16_t time)
{
if (Speed > 100)
{
Speed = 100;
}
else if (Speed < 0)
{
Speed = 0;
}
Motor_SetSpeed(0,Speed,Speed,0); //可以从输出比较的图看出来
Delay_ms(time);
Motor_SetSpeed(0,0,0,0); //最后车子停止运动
}
//车子右转
void Motor_TurnRight(uint8_t Speed,uint16_t time)
{
if (Speed > 100)
{
Speed = 100;
}
else if (Speed < 0)
{
Speed = 0;
}
Motor_SetSpeed(Speed,0,0,0); //可以从输出比较的图看出来
Delay_ms(time);
Motor_SetSpeed(0,0,0,0); //最后车子停止运动
}
//小车右旋转
void Motor_Spin_Right(uint8_t Speed,uint16_t time)
{
if (Speed > 100)
{
Speed = 100;
}
else if (Speed < 0)
{
Speed = 0;
}
Motor_SetSpeed(Speed,0,0,Speed); //可以从输出比较的图看出来
Delay_ms(time);
Motor_SetSpeed(0,0,0,0); //最后车子停止运动
}
//小车刹车
void Motor_Brake(uint16_t time)
{
Motor_SetSpeed(0,0,0,0);
Delay_ms(time);
}
#小车的蓝牙模块—Serial(串口)
1.通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
2.通信协议:制定通信的规则,通信双方按照协议规则进行数据收发
##串口的性质
USART:
1.引脚—TX和RX 2.双工—全双工(发送双方可以同时收发数据) 3.时钟:异步 4.电平:单端 5.设备:点对点—就是说只能双方进行通信
串口接线是交叉的,蓝牙串口不是独立供电的,所以我们是要将蓝牙模块与stm32连接,以stm32供电给蓝牙。
##USART(串口)性质
1.USART,(Universal Synchronous/Asynchronous Receiver/Transmitte)通用同步/异步收发器
2.USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
3.自带波特率发生器,最高达4.5Mbits/s
4.可配置数据为长度(8/9),停止位长度(0.5/1/1.5/2)
5.可选校验位(无校验/奇校验/偶校验)
6.支持同步模式,硬件流控制,DMA,智能卡,IrDA,LIN
7.STM32F103C8T6 USART资源:USART1,USART2,USART3 在这里,商家给我指定了串口资源—USART3,所以后面的代码篇用到的都为USART3
##Serial代码篇(\Hardware)
1.Serial.h
#ifndef __SERIAL_H__
#define __SERIAL_H__
//接收数据的结构体
typedef struct
{
uint8_t Data[100]; //这个是用来接收文本数据的
uint8_t flag; //这个是后面main里面判断要用到的
uint8_t Length; //接收到的文本数据的大小
}MyUsart;
extern MyUsart MYUSART3;
void Serial_Init(void);
#endif
2.Serial.c
#include "stm32f10x.h" // Device header
#include "Serial.h" //在.h文件里面定义的结构体,你需要在这个文件里面引用
MyUsart MYUSART3;
// 1.GPIO的配置
//2.USART的配置
//3.NVIC的配置 接收文本
void Serial_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); //USART3
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//GPIO配置
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//NIVC中断配置
NVIC_InitTypeDef NVIC_InitStruct; //谁来触发中断
NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART3,USART_IT_RXNE,ENABLE); //配置USART中断如何触发 接收中断
USART_ITConfig(USART3,USART_IT_IDLE,ENABLE); //空闲中断
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 9600;
USART_InitStruct.USART_HardwareFlowControl = DISABLE;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收和发送
USART_InitStruct.USART_Parity = USART_Parity_No; //不校验
USART_InitStruct.USART_StopBits = 8;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART3,&USART_InitStruct);
USART_Cmd(USART3,ENABLE);
}
//中断函数
void USART3_IRQHandler(void)
{
//接收判断
if (USART_GetITStatus(USART3,USART_IT_RXNE) == SET)
{
USART_ClearITPendingBit(USART3,USART_IT_RXNE); //清除后为了下一次接收数据做准备
MYUSART3.Data[MYUSART3.Length++] = USART_ReceiveData(USART3); //我接收好一次数据后,指针指向新的位置
}
if (USART_GetITStatus(USART3,USART_IT_IDLE) == SET)
{
MYUSART3.Data[MYUSART3.Length] = '\0'; //字符串的最后一位是'\0'
MYUSART3.flag = 1;
MYUSART3.Length = 0;
USART_ReceiveData(USART3);
}
//空闲判断
}
//发送函数
这样,我们便完成了Serial的定义,我们继续再main.c里面完成编码
#蓝牙小车的最终引用
#include "stm32f10x.h" // Device header
#include "Serial.h"
#include "Motor.h"
#include <string.h>
#include <stdio.h>
#include "Myu3.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组分2组
// Serial_Init();
USART3_init(9600);
Motor_Init();
while(1)
{
if (MyUsart3.flag)
{
MyUsart3.flag = 0;
if (strcmp((const char*)MyUsart3.buff,"ONA") == 0)
{
Motor_Run(80,100);
}
if (strcmp((const char*)MyUsart3.buff,"ONB") == 0)
{
Motor_Back(80,100);
}
if (strcmp((const char*)MyUsart3.buff,"ONC") == 0)
{
Motor_Spin_Left(80,100);
}
if (strcmp((const char*)MyUsart3.buff,"OND") == 0)
{
Motor_Spin_Right(80,100);
}
if (strcmp((const char*)MyUsart3.buff,"ONF") == 0)
{
Motor_Brake(100);
}
if (strcmp((const char*)MyUsart3.buff,"ONE") == 0)
{
Motor_Brake(100);
}
}
}
}
#致谢
最后,感谢你阅读完整个Blog,希望我的文章对你有所启发,有所帮助。感谢!