1、首先介绍一下什么是透明传输:
透明传输是指不管所传数据是什么样的比特组合,都应当能够在链路上传送。当所传数据中的比特组合恰巧与某一个控制信息完全一样时,就必须采取适当的措施,使接收方不会将这样的数据误认为是某种控制信息。这样才能保证数据链路层的传输是透明的。
发送方和接收方数据的长度和内容完全一致,相当于一条无形的传输线。
2、透传模块:
Handler_Bluetooth_PWM平台上使用的是FBT_06(嵌入式近距离主从分离/一体式蓝牙串口通讯模块)
特点:
蓝牙 2.0 带 EDR, 2Mbps-3Mbps 调制度
内置 2.4GHz 天线, 用户无需调试天线
外置 8Mbit FLASH
低电压 3.3V 工作(3.1V~4.2V)配对时 30~40MA 波动,配对完毕通信 8MA
可选 PIO 控制
标准 HCI 端口(UART or USB)
USB 协议: Full Speed USB1.1, Compliant With 2.0
发射功率属于class2(约15米)
3、Handler_Bluetooth_PWM平台(引用)
该平台由Handler_Studio设计,初始目的是专门为四轴飞行器等陆地、空中机器人提供处理核心,而后被大家将其扩展到了各个领域,进行了五花八门的设计应用。
该平台基于stm32进行研发设计,接口人性化,相比市面上的常见的multiwill平台的mega328、mega2560等飞控具有主频高、处理速度快、性能稳定等显著优点。
4、蓝牙透传实例
本文档给大家讲述如何用借助Handler_Bluetooth_PWM平台通过手机控制产生多路PWM以控制舵机!
说明:
硬件平台:Handler_Bluetooth_PWM飞控板
开发环境:keilMDK
Stm库版本:V3.0.0
首先需要配置stm32的串口:
1、使能时钟
2、使能串口中断
3、初始化串口
这里想重点说明一下串口数据的接收策略:
不怕麻烦举四个例子说明,如果很了解就跳过,初学者还是有作用的(欢迎拍砖)
实例一:
void USART1_IRQHandler(u8 GetData)
{
u8 BackData;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //中断产生
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志.
GetData = UART1_GetByte(BackData); //也行GetData=USART1->DR;
USART1_SendByte(GetData); //发送数据
GPIO_SetBits(GPIOE, GPIO_Pin_8 ); //LED闪烁,接收成功发送完成
delay(1000);
GPIO_ResetBits(GPIOE, GPIO_Pin_8 );
}
}
这是最基本的,将数据接收完成后又发送出去,接收和发送在中断函数里执行,main函数里无其他要处理的。
优点:简单,适合很少量数据传输。
缺点:无缓存区,并且对数据的正确性没有判断,数据量稍大可能导致数据丢失 。
实例二:
void USART2_IRQHandler()
{
if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志
Uart2_Buffer[Uart2_Rx_Num] = USART_ReceiveData(USART2);
Uart2_Rx_Num++;
}
if((Uart2_Buffer[0] == 0x5A)&&(Uart2_Buffer[Uart2_Rx_Num-1] == 0xA5)) //判断最后接收的数据是否为设定值,确定数据正确性
Uart2_Sta=1;
if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) //溢出
{
USART_ClearFlag(USART2,USART_FLAG_ORE); //读SR
USART_ReceiveData(USART2); //读DR
}
}
if( Uart2_Sta )
{
for(Uart2_Tx_Num=0;Uart2_Tx_Num < Uart2_Rx_Num;Uart2_Tx_Num++)
USART2_SendByte(Uart2_Buffer[Uart2_Tx_Num]); //发送数据
Uart2_Rx_Num = 0; //初始化
Uart2_Tx_Num = 0;
Uart2_Sta = 0;
}
这是加了数据头和数据尾的接收方式,数据头和尾的个数可增加,此处只用于调试之用。中断函数用于接收数据以及判断数据的头尾,第二个函数在main函数里按照查询方式执行。
优点:较简单,采用缓存区接收,对提高数据的正确行有一定的改善 。
缺点:要是第一次数据接收错误,回不到初始化状态,必须复位操作 。
实例三:
void USART2_IRQHandler()
{
if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志.
Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2);
Uart2_Rx++;
Uart2_Rx &= 0x3F; //判断是否计数到最大
}
if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) //溢出
{
USART_ClearFlag(USART2,USART_FLAG_ORE); //读SR
USART_ReceiveData(USART2); //读DR
}
}
if( Uart2_Tx != Uart2_Rx )
{
USART2_SendByte(Uart2_Buffer[Uart2_Tx]); //发送数据
Uart2_Tx++;
Uart2_Tx &= 0x3F; //判断是否计数到最大
}
采用FIFO方式接收数据,由0x3F可知此处最大接收量为64个,可变,中断函数只负责收,另一函数在main函数里执行,FIFO方式发送。
优点:发送和接收都很自由,中断占用时间少,有利于MCU处理其它。
缺点:对数据的正确性没有判断,一概全部接收。
实例四:
void USART2_IRQHandler()
{
if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志
Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2);
Uart2_Rx++;
Uart2_Rx &= 0xFF;
}
if(Uart2_Buffer[Uart2_Rx-1] == 0x5A) //头
Uart2_Tx = Uart2_Rx-1;
if((Uart2_Buffer[Uart2_Tx] == 0x5A)&&(Uart2_Buffer[Uart2_Rx-1] == 0xA5)) // 检测到头的情况下检测到尾
{
Uart2_Len = Uart2_Rx-1- Uart2_Tx; //长度
Uart2_Sta=1; //标志位
}
if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) //溢出
{
USART_ClearFlag(USART2,USART_FLAG_ORE); //读SR
USART_ReceiveData(USART2); //读DR
}
}
if( Uart2_Sta )
{
for(tx2=0;tx2 <= Uart2_Len;tx2++,Uart2_Tx++)
USART2_SendByte(Uart2_Buffer[Uart2_Tx]); //发送数据
Uart2_Rx = 0; //初始化
Uart2_Tx = 0;
Uart2_Sta = 0;
}
数据采用数据包的形式接收,接收后存放于缓存区,通过判断数据头和数据尾(可变)来判断数据的“包”及有效性,中断函数用于接收数据和判断头尾以及数据包长度,另一函数在main函数里执行,负责发送该段数据。
优点:适合打包传输,稳定性和可靠性很有保证,可随意发送,自动挑选有效数据。
缺点:缓存区数据长度要根据“包裹”长度设定, 要是多次接收后无头无尾,到有头有尾的那一段数据恰好跨越缓存区最前和最后位置时,可能导致本次数据丢失,不过这种情况几乎没有可能。
不论使用哪种方法,都是可以实现简单的功能的,一般来说中断里面只负责接收数据,而不要在其进行数据的处理。
接下来需要设置stm32的TIM模块
由于平台上的处理器是STM32CBT6,定时器有4个,分别是一个高级控制定时器TIM1,和三个通用定时器。
TIMER输出PWM实现步骤:
1. 设置RCC时钟;
2. 设置GPIO时钟;
3. 设置TIMx定时器的相关寄存器;
4. 设置TIMx定时器的PWM相关寄存器。
附上源码(以TIM2为例:两个通道)
/*
* 函数名:TIM2_GPIO_Config
* 描述 :配置TIM3复用输出PWM时用到的I/O
* 输入 :无
* 输出 :无
* 调用 :内部调用
*/
static void TIM2_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* TIM3 clock enable */
//PCLK1经过2倍频后作为TIM3的时钟源等于36MHz
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* GPIOA and GPIOB clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
/*GPIOA Configuration: TIM3 channel 1 and 2 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
static void TIM2_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* PWM信号电平跳变值 */
u16 CCR1_Val = 251;
u16 CCR2_Val = 251;
/* -----------------------------------------------------------------------
TIM2 Configuration: generate 4 PWM signals with 4 different duty cycles:
TIM2CLK = 36 MHz, Prescaler = 0x0, TIM3 counter clock = 36 MHz
TIM2 ARR Register = 999 => TIM2 Frequency = TIM2 counter clock/(ARR + 1)
TIM2 Frequency = 36 KHz.
----------------------------------------------------------------------- */
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 9999; //当定时器从0计数到999,即为1000次,为一个定时周期
TIM_TimeBaseStructure.TIM_Prescaler = 144; //设置预分频:不预分频,即为36MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分频系数:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val; //设置跳变值,当计数器计数到这个 值时,电平发生跳变
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //当定时器计数值小 于CCR1_Val时为高电平
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //使能通道1
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
/* PWM1 Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val; //设置通道2的电平跳变值,输出另外一个占空比的PWM
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //使能通道2
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE); // 使能TIM2重载寄存器ARR
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE); //使能定时器2
}
/*
* 函数名:TIM2_Mode_Config
* 描述 :TIM2 输出PWM信号初始化,只要调用这个函数
* TIM2的两个通道就会有PWM信号输出
* 输入 :无
* 输出 :无
* 调用 :外部调用
*/
void TIM2_PWM_Init(void)
{
TIM2_GPIO_Config();
TIM2_Mode_Config();
}
在配置其他两个通用定时器时,配置几乎是相同的,但是在配置TIM1的时候需要加这个函数以使能TIM1的PWM输出
TIM_CtrlPWMOutputs(TIM1, ENABLE);
这样这个工程的基本功能就都具备了,接下来只需要处理手机端发过来的数据进行调节PWM输出即可。数据可以自定协议。这里简单的说明一下,由于平台上有10个通道所以我规定手机端发送过来的数据在十个数据之间要有所区别,我设定发送格式为Aa1000B
其中A和B是数据包的首和尾,a代表第一通道,相应的有b通道、c通道...1000代表占空比。
这样一个基于Handler_Bluetooth_PWM平台的通过手机产生多路PWM控制舵机的程序就完成了。
|