STM32无线遥控小车设计与制作指南
目录
模块设计
一、具体的接线图
1、无线遥控器端
(1)NRF24L01与STM32接线
(2)PS2手柄摇杆与STM32接线
2、小车端
(1)NRF24L01与STM32接线
(2)TB6612与STM32接线
(3)TB6612与直流电机接线
二、主控的选择
三、怎么实现无线遥控?
1、发送端
2、接收端
四、无线遥控模块程序的编写
1、NRF24L01程序编写
(1)配置NRF24L01寄存器地址
(2)NRF24L01 模块初始化配置
2、ADC采集PS2手柄遥控模拟值
3、主程序的编写
五、小车程序的编写
1、NRF24L01程序编写
2、PWN输出函数的编写
3、电机控制函数的编写
4、主程序的编写
模块设计
本次设计使用到的模块有:12锂电池1;12V转3.3V和5V电源模块;两个NRF24L01模块;两个STM32F103C8T6;PS2手柄摇杆(带回中);两个直流电机;TB6612电机驱动模块
后续更新—–
一、具体的接线图
1、无线遥控器端
(1)NRF24L01与STM32接线
NRF24L01引脚图:
IRQ ——– PB11 CE ——– PB12 CSN——– PB10
SCK ——– PB13 MOSI ——– PB15 MISO ——– PB14
(2)PS2手柄摇杆与STM32接线
控制前进和后退的摇杆:
VRy —– PA2 GND —– GND +5V —– 5V
控制左转和右转的摇杆:
VRx —– PA6 GND —– GND +5V —– 5V
其余引脚可不接,看你需求接。
2、小车端
(1)NRF24L01与STM32接线
IRQ ——– PB1 CE ——– PB0 CSN——– PA4
SCK ——– PA5 MOSI ——– PA7 MISO ——– PA6
(2)TB6612与STM32接线
VM ——– 5V VCC ——– 3.3V GND——– GND STBY —– 5V
AIN1 ——– PA0 AIN2 ——– PA1 PWMA ——– PA2
BIN1 ——– PB14 BIN2 ——– PB15 PWMB ——– PA3
(3)TB6612与直流电机接线
AO1和AO2任意接两根电机线,BO1和BO2同理。
二、主控的选择
本次设计采用STM32F103系列芯片,STM32F103C8T6是一款基于ARM Cortex-M 内核STM32系列的32位的微控制器,程序存储器容量是64KB,需要电压2V~3.6V,工作温度为-40°C ~ 85°C。
三、怎么实现无线遥控?
结合上一篇SPI通信的博文,本次设计采用的是SPI通信的方式实现无线遥控的功能。SPI具体的介绍,请看基于STM32的SPI通信和IIC通信协议学习笔记-CSDN博客
本次设计采用NRF24L01模块实现的无线遥控功能,下图是模块的示意图:
实现的具体思路:使用一个STM32F103系统板连接NRF24L01,作为发送端(即无线遥控器);另外使用一个 STM32F103系统板连接NRF24L01,作为接收端(小车)。通过发送数据给小车,实现小车前进,后退,左转,右转等功能。
1、发送端
通过ADC的采样,采集PS2手柄摇杆模块的模拟值,进行一系列处理后,使用SPI通信发送数据给接收端。
2、接收端
通过对接收到的数据进行计算处理成合适的速度值,将处理好的速度值放进电机驱动函数里面,实现电机的正转和反转,从而实现小车的基本功能。
四、无线遥控模块程序的编写
1、NRF24L01程序编写
打开keil5界面,添加以下三个文件:
(1)配置NRF24L01寄存器地址
引脚仅供参考,可以自行修改
NRF24L01_Ins.h 文件:
#ifndef __nRF24L01_H
#define __nRF24L01_H
/********** NRF24L01引脚定义 ***********/
#define IRQ_Port GPIOB
#define CE_Port GPIOB
#define CSN_Port GPIOB
#define SCK_Port GPIOB
#define MOSI_Port GPIOB
#define MISO_Port GPIOB
#define IRQ_Pin GPIO_Pin_11
#define CE_Pin GPIO_Pin_12
#define CSN_Pin GPIO_Pin_10
#define SCK_Pin GPIO_Pin_13
#define MOSI_Pin GPIO_Pin_15
#define MISO_Pin GPIO_Pin_14
/********** NRF24L01寄存器操作命令 ***********/
#define nRF_READ_REG 0x00
#define nRF_WRITE_REG 0x20
#define RD_RX_PLOAD 0x61
#define WR_TX_PLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define NOP 0xFF
/********** NRF24L01寄存器地址 *************/
#define CONFIG 0x00
#define EN_AA 0x01
#define EN_RXADDR 0x02
#define SETUP_AW 0x03
#define SETUP_RETR 0x04
#define RF_CH 0x05
#define RF_SETUP 0x06
#define STATUS 0x07
#define OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define FIFO_STATUS 0x17
/****** STATUS寄存器bit位定义 *******/
#define MAX_TX 0x10 //达到最大发送次数中断
#define TX_OK 0x20 //TX发送完成中断
#define RX_OK 0x40 //接收到数据中断
#endif
(2)NRF24L01 模块初始化配置
设置地址宽度和有效数据宽度:定义发送和接收地址的宽度分别为 5 字节,有效数据宽度为 32 字节。
定义地址值:定义发送和接收地址的数值,用于与目标设备通信。
实现改变引脚电平的函数:
NRF24L01.c 文件第一部分代码:
# include "NRF24L01.h"
#include "NRF24L01_Ins.h"
#include "stm32f10x.h" // Device header
#include "Delay.h"
#define TX_ADR_WIDTH 5 //5字节地址宽度
#define RX_ADR_WIDTH 5 //5字节地址宽度
#define TX_PLOAD_WIDTH 32 //32字节有效数据宽度
#define RX_PLOAD_WIDTH 32 //32字节有效数据宽度
const uint8_t TX_ADDRESS[TX_ADR_WIDTH]={0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
const uint8_t RX_ADDRESS[RX_ADR_WIDTH]={0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
//改变SS引脚的输出电平
void W_SS(uint8_t BitValue)
{
GPIO_WriteBit(CSN_Port, CSN_Pin, (BitAction)BitValue);
}
//改变CE引脚的输出电平
void W_CE(uint8_t BitValue)
{
GPIO_WriteBit(CE_Port, CE_Pin, (BitAction)BitValue);
}
//改变SCK引脚的输出电平
void W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(SCK_Port, SCK_Pin, (BitAction)BitValue);
}
//改变MOSI引脚的输出电平
void W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(MOSI_Port, MOSI_Pin,(BitAction)BitValue);
}
//读取MISO引脚的输出电平
uint8_t R_MISO(void)
{
return GPIO_ReadInputDataBit(MISO_Port, MISO_Pin);
}
//读取IRQ引脚的输出电平
uint8_t R_IRQ(void)
{
return GPIO_ReadInputDataBit(IRQ_Port, IRQ_Pin);
}
NRF24L01.c 文件第二部分代码:
//初始化NRF24L01使用的引脚
//其中CSN、SCK、MOSI、CE设置为推挽输出 IRQ、MISO设置为上拉输入模式
void NRF24L01_Pin_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = CSN_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(CSN_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = SCK_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SCK_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = MOSI_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MOSI_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = CE_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(CE_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = MISO_Pin;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(MISO_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = IRQ_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IRQ_Port, &GPIO_InitStructure);
}
//标准SPI交换一个字节
uint8_t SPI_SwapByte(uint8_t Byte)
{
uint8_t i;
for(i = 0; i < 8; i ++)
{
if((uint8_t)(Byte & 0x80) == 0x80)
{
W_MOSI(1);
}
else
{
W_MOSI(0);
}
Byte = (Byte << 1);
W_SCK(1); //模拟时钟上升沿
Byte |= R_MISO(); //接收从机发来的一个数据
W_SCK(0); //模拟时钟下降沿
}
return Byte;//交换从机的一个完整字节并返回
}
//通过调用SPI与NRF24L01交换一个字节
//Reg:交换的第一个字节就是从机的寄存器地址
//Value:交换的第二个字节就是要在这个寄存器写入的数据
uint8_t NRF24L01_Write_Reg(uint8_t Reg, uint8_t Value)
{
uint8_t Status;
W_SS(0);
Status = SPI_SwapByte(Reg);
SPI_SwapByte(Value);
W_SS(1);
return Status;
}
//通过调用SPI读取NRF24L01的一个字节
//Reg:交换的第一个字节就是要读取的从机的寄存器地址
//Value:再次调用SPI的交换函数,用NOP交换会寄存器的储存的数据并用Value接收并返回
uint8_t NRF24L01_Read_Reg(uint8_t Reg)
{
uint8_t Value;
W_SS(0);
SPI_SwapByte(Reg);
Value = SPI_SwapByte(NOP);
W_SS(1);
return Value;
}
//在指定的寄存器写入一堆数据,和上面函数的关系上面的函数只写一个,这个可以写很多个
//Reg:要读取的寄存器地址
//*Buf:要写入寄存器的的数据的首地址
//Len:要写入的数据长度
uint8_t NRF24L01_Write_Buf(uint8_t Reg, uint8_t *Buf, uint8_t Len)
{
uint8_t Status, i;
W_SS(0);
Status = SPI_SwapByte(Reg);
for(i = 0; i < Len; i ++)
{
SPI_SwapByte(*Buf ++);
}
W_SS(1);
return Status;
}
//在指定的寄存器读取一堆数据,和上面函数的区别就是上面的读一个这个能读很多个
//Reg:要读取的寄存器地址
//*Buf:用于存储读取到的数据的数组的首地址
//Len:要读取的数据的长度
uint8_t NRF24L01_Read_Buf(uint8_t Reg, uint8_t *Buf, uint8_t Len)
{
uint8_t Status, i;
W_SS(0); //下拉SS表示选中从机
Status =SPI_SwapByte(Reg);
for(i = 0; i < Len; i ++)
{
Buf[i] = SPI_SwapByte(NOP);
}
W_SS(1); //拉高SS表示通讯结束
return Status; //返回状态值作用不大
}
//通过调用函数向WR_TX_PLOAD寄存器写入要发送的数据(在WR_TX_PLOAD寄存器中写入的数据就是要发送的数据)
//*Buf:要写入寄存器发射的数据的数组的首地址,也就是要发送的数据,最多可以发送32个字节
uint8_t NRF24L01_SendTxBuf(uint8_t *Buf)
{
uint8_t State;
W_CE(0); //CE置0调为发射模式
NRF24L01_Write_Buf(WR_TX_PLOAD, Buf, TX_PLOAD_WIDTH);
W_CE(1); //CE置1发射
while(R_IRQ() == 1); //检测中断位
State = NRF24L01_Read_Reg(STATUS);
NRF24L01_Write_Reg(nRF_WRITE_REG + STATUS, State); //清除中断位
if(State&MAX_TX) //判断是否发射达到最大次数触发中断
{
NRF24L01_Write_Reg(FLUSH_TX, NOP);
return MAX_TX;
}
if(State & TX_OK) //判断是否发射成功触发中断
{
return TX_OK;
}
return NOP; //其他情况返回NOP
}
//通过调用函数读取RD_RX_PLOAD寄存器读取接收到的数据(在RD_RX_PLOAD寄存器中储存的就是接收到的数据)
//*Buf:储存读取到的数据的首地址
//和上面的函数相比没有CE的设置,因为默认状态下CE为高电平,就是默认的接收模式
uint8_t NRF24L01_GetRxBuf(uint8_t *Buf)
{
uint8_t State;
State = NRF24L01_Read_Reg(STATUS); //读取STATUS状态寄存器判读触发的中断类型
NRF24L01_Write_Reg(nRF_WRITE_REG + STATUS, State); //再次在STATUS寄存器的位置写入1可以重置中断
if(State & RX_OK)
{
W_CE(1); //为了确保是接收模式,假设不写也没关系,但是写了更保险
NRF24L01_Read_Buf(RD_RX_PLOAD, Buf, RX_PLOAD_WIDTH);
NRF24L01_Write_Reg(FLUSH_RX, NOP);
W_CE(1);
Delay_us(150); //适当延迟以延长接收时间
return 0;
}
return 1;
}
//用于检测
uint8_t NRF24L01_Check(void)
{
uint8_t check_in_buf[5] = {0x11 ,0x22, 0x33, 0x44, 0x55};
uint8_t check_out_buf[5] = {0x00};
//保证SCK、SS、CE被初始化
W_SCK(0);
W_SS(1);
W_CE(0);
//将check_in_buf数组中的数据写入TX_ADDR发送地址寄存器中
NRF24L01_Write_Buf(nRF_WRITE_REG + TX_ADDR, check_in_buf, 5);
//读取TX_ADDR寄存器里的数据并储存在check_out_buf中
NRF24L01_Read_Buf(nRF_READ_REG + TX_ADDR, check_out_buf, 5);
//鉴定check_out_buf中储存的数据和写入的check_in_buf中的一样,如果一样说明NRF24L01已经连接
if((check_out_buf[0] == 0x11) && (check_out_buf[1] == 0x22) && (check_out_buf[2] == 0x33) && (check_out_buf[3] == 0x44) && (check_out_buf[4] == 0x55))
{
return 0; //如果检测成功返回0
}
else
{
return 1; //检测失败返回1
}
}
//初始化NRF24L01的发送或者接收状态
void NRF24L01_RT_Init(void)
{
W_CE(0); //以下可能要进行接收或者发射模式的转换,模式转换时CE引脚要先置0
//初始化设置接收数据的数据宽度(为32字节)
NRF24L01_Write_Reg(nRF_WRITE_REG+RX_PW_P0, RX_PLOAD_WIDTH);
//调用FLUSH_RX指令可以清空接收缓存区,发送的NOP没用
NRF24L01_Write_Reg(FLUSH_RX, NOP);
//写入TX_ADDR寄存器的是发送的数据的接收地址,TX_ADR_WIDTH为固定的地址长度(长度为5个字节)
NRF24L01_Write_Buf(nRF_WRITE_REG + TX_ADDR, (uint8_t*)TX_ADDRESS, TX_ADR_WIDTH);
//写入RX_ADDR_P0确认第一个接收地址(最多可以有6个),RX_ADR_WIDTH为固定的地址长度(长度为5个字节)
NRF24L01_Write_Buf(nRF_WRITE_REG + RX_ADDR_P0, (uint8_t*)RX_ADDRESS, RX_ADR_WIDTH);
//启动自动应答
NRF24L01_Write_Reg(nRF_WRITE_REG + EN_AA, 0x01);
//使能数据接收的通道,这里只使用通道0,因此在EN_RXADDR寄存器中写入0000 0001
NRF24L01_Write_Reg(nRF_WRITE_REG + EN_RXADDR, 0x01);
//配置SETUP_RETR寄存器 0001 1010 高四位表示延时槽,0001:表示两个延时槽(250×2=86=586us) 1010:表示重发10次
NRF24L01_Write_Reg(nRF_WRITE_REG + SETUP_RETR, 0x1A);
//工作频率=(2400 + RF_CH) MHz,在这个寄存器写0表示工作频率为2.4G
NRF24L01_Write_Reg(nRF_WRITE_REG + RF_CH, 0);
//配置寄存器0000 1111 配置只有接收到对应地址触发中断,默认进入接收模式,且发射结束后自动进入接收模式接收应答信号
//B东西无脑配成0x0F就行
//NRF24L01_Write_Reg(nRF_WRITE_REG + RF_SETUP, 0x0F);
//CONFIG寄存器就这个样配置(默认配置为接收模式)
NRF24L01_Write_Reg(nRF_WRITE_REG + CONFIG, 0x0F);
W_CE(1);
}
//初始化
//引脚初始化,检查,NRF24L01寄存器初始化
void NRF24L01_Init()
{
NRF24L01_Pin_Init();
while(NRF24L01_Check());
NRF24L01_RT_Init();
}
//
void NRF24L01_SendBuf(uint8_t *Buf)
{
W_CE(0); //模式转换
//CONFIG最低位置0,配置为发射模式
NRF24L01_Write_Reg(nRF_WRITE_REG + CONFIG, 0x0E);
W_CE(1);
Delay_us(15);//15us之后进入发射模式
NRF24L01_SendTxBuf(Buf);//将数据写入寄存器中发射
W_CE(0);//模式转换
NRF24L01_Write_Reg(nRF_WRITE_REG + CONFIG, 0x0F);
W_CE(1);
}
//看看NRF24L01是不是触发中断
uint8_t NRF24L01_Get_Value_Flag()
{
return R_IRQ();
}
NRF24L01.h 文件代码:
#ifndef __nRF24L01_API_H
#define __nRF24L01_API_H
#include "stm32f10x.h" // Device header
uint8_t SPI_SwapByte(uint8_t byte);
uint8_t NRF24L01_Write_Reg(uint8_t reg,uint8_t value);
uint8_t NRF24L01_Read_Reg(uint8_t reg);
uint8_t NRF24L01_Read_Buf(uint8_t reg,uint8_t *pBuf, uint8_t len);
uint8_t NRF24L01_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len);
uint8_t NRF24L01_GetRxBuf(uint8_t *rxbuf);
uint8_t NRF24L01_SendTxBuf(uint8_t *txbuf);
uint8_t NRF24L01_Check(void);
void NRF24L01_RT_Init(void);
void NRF24L01_Init(void);
void NRF24L01_SendBuf(uint8_t *Buf);
void NRF24L01_Pin_Init(void);
uint8_t NRF24L01_Get_Value_Flag();
#endif
到这里,无线遥控器的NRF24L01的程序已经写完了,接下来是ADC函数的配置。
2、ADC采集PS2手柄遥控模拟值
ADC函数的详细配置请看博客:STM32 ADC转换器、串口输出_stm32 adf输出-CSDN博客
后续我会补充这里。
3、主程序的编写
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "AD.h"
#include "NRF24L01.h"
uint8_t Speed = 0x00;
uint8_t Direction = 0x00;
uint8_t Control[32];
int main(void)
{
/*模块初始化*/
AD_Init(); //AD初始化
NRF24L01_Init(); //NRF24L01初始化
while (1)
{
//定义8位的速度和方向,用于控制电机和舵机
//要发送的信息只有两个
Control[0]=2;
//用数组接收速度和方向的参数用于发送
Control[1]= Speed = (uint8_t)(AD_Value[1]>>4);
Control[2]=Direction=(uint8_t)(AD_Value[2]>>4);
NRF24L01_SendBuf(Control);
Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间
}
}
将模拟输入的 AD 值进行处理,然后将处理后的数值存储到 Control 数组中。
//用数组接收速度和方向的参数用于发送
Control[1]= Speed = (uint8_t)(AD_Value[1]>>4);
Control[2]=Direction=(uint8_t)(AD_Value[2]>>4);
为什么获取到的模拟值要做移位处理呢? 我们来分析一下:
假设我们有一个模拟输入传感器,其输出范围是 0 到 1023(10 位精度)。我们想要将这个范围内的数值转换为一个更适合用于控制系统的范围,比如 0 到 255(8 位精度)。这时候可以利用右移位操作来处理这些数值。
例如,假设传感器输出的数值为 682。在代码中进行右移 4 位(即 AD_Value >> 4
)后,682 变成了 42。这个处理后的数值 42 可以更好地映射到控制系统所需的范围内(0 到 255),方便后续控制逻辑的处理。学到了哈哈哈。
五、小车程序的编写
1、NRF24L01程序编写
NRF24L01.c文件和 NRF24L01_Ins.h 文件 的配置 与无线遥控器的程序一样,这里不做多记录了。
唯一改变的是NRF24L01.h中的引脚定义部分改为以下引脚:
/********** NRF24L01引脚定义 ***********/
#define IRQ_Port GPIOB
#define CE_Port GPIOB
#define CSN_Port GPIOA
#define SCK_Port GPIOA
#define MOSI_Port GPIOA
#define MISO_Port GPIOA
#define IRQ_Pin GPIO_Pin_1
#define CE_Pin GPIO_Pin_0
#define CSN_Pin GPIO_Pin_4
#define SCK_Pin GPIO_Pin_5
#define MOSI_Pin GPIO_Pin_7
#define MISO_Pin GPIO_Pin_6
2、PWN输出函数的编写
定时器的具体配置流程请参考:
pwm.c 文件:
#include "stm32f10x.h" // Device header
/**
* 函 数:PWM初始化
* 参 数:使用 PA2-TIM2_CH3 & PA3-TIM2_CH4
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
TIM_OC4Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC4Init,配置TIM2的输出比较通道4
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR3的值
}
void PWM_SetCompare4(uint16_t Compare)
{
TIM_SetCompare4(TIM2, Compare); //设置CCR4的值
}
头文件:这里就不写出来了,只需把相关函数放进去就好了。
3、电机控制函数的编写
通过操作PS2手柄摇杆 ,接收端获取发送端传输的数据后,进行一系列的处理操作,将其转换成合适的速度值,放进Motor_go_SetSpeed(int8_t Speed)和Motor_LR_SetSpeed(int8_t Direction)函数中,实现控制引脚的高低电平和PWM占空比的设置,实现小车的基本功能。
Motor.c 文件:
#include "stm32f10x.h" // Device header
#include "PWM.h"
/**
* 函 数:左路直流电机初始化
* 参 数:无
* 返 回 值:无
*/
void MotorL_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4和PA5引脚初始化为推挽输出
PWM_Init(); //初始化直流电机的底层PWM
}
/**
* 函 数:右路直流电机初始化
* 参 数:无
* 返 回 值:无
*/
void MotorR_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14和PB15引脚初始化为推挽输出
PWM_Init(); //初始化直流电机的底层PWM
}
/**
* 函 数:直流电机设置速度
* 参 数:Speed 要设置的速度,范围:-100~100
* 返 回 值:无
*/
void Motor_go_SetSpeed(int8_t Speed)
{
if (Speed >= 127) //如果设置正转的速度值
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0); //PA4置高电平
GPIO_SetBits(GPIOA, GPIO_Pin_1); //PA5置低电平,设置方向为正转
PWM_SetCompare3(Speed); //PWM设置为速度值
GPIO_ResetBits(GPIOB, GPIO_Pin_14); //PA4置高电平
GPIO_SetBits(GPIOB, GPIO_Pin_15); //PA5置低电平,设置方向为正转
PWM_SetCompare4(Speed); //PWM设置为速度值
}
else if(Speed < 127) //否则,即设置反转的速度值
{
GPIO_SetBits(GPIOA, GPIO_Pin_0); //PA4置低电平
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //PA5置高电平,设置方向为反转
PWM_SetCompare3(-Speed); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
GPIO_SetBits(GPIOB, GPIO_Pin_14); //PA4置低电平
GPIO_ResetBits(GPIOB, GPIO_Pin_15); //PA5置高电平,设置方向为反转
PWM_SetCompare4(-Speed); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
}
}
void Motor_LR_SetSpeed(int8_t Direction)
{
if (Direction >= 57) //如果设置左转的速度值
{
GPIO_ResetBits(GPIOB, GPIO_Pin_14); //PA4置高电平
GPIO_SetBits(GPIOB, GPIO_Pin_15); //PA5置低电平,设置方向为正转
PWM_SetCompare4(Direction); //PWM设置为速度值
}
else if(Direction < -57) //否则,即设置反转的速度值
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0); //PA4置低电平
GPIO_SetBits(GPIOA, GPIO_Pin_1); //PA5置高电平,设置方向为反转
PWM_SetCompare3(Direction); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
}
}
4、主程序的编写
main.c 文件:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "NRF24L01.h"
#include "MOTOR.h"
int8_t Speed; //定义速度变量
int8_t Direction;
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
NRF24L01_Init(); //NRF24L01的初始化
MotorL_Init(); //左路两个电机
MotorR_Init(); //右路两个电机
uint8_t Buf[32] = {0}; //定义用于接收NRF24L01数据的数组
OLED_ShowString(1, 6, "Len:"); //OLED显示
OLED_ShowString(2, 6, "Y:");
OLED_ShowString(3, 6, "X:");
while (1)
{
if (NRF24L01_Get_Value_Flag() == 0)
{
NRF24L01_GetRxBuf(Buf);
}
Speed=Buf[1] * 20 / 26 - 100;
Direction=Buf[2] * 20 / 26 - 100;
// OLED_ShowNum(1, 10, Buf[0], 2);
OLED_ShowSignedNum(2, 8, Speed, 3);
OLED_ShowSignedNum(3, 8, Direction, 3);
if (Speed)
{
Motor_go_SetSpeed(Speed); //小车的前进和后退
}
if(Direction)
{
Motor_LR_SetSpeed(Direction); //小车的左转和右转
}
}
}
待完善—
作者:某不知名大学生X