系列文章目录
一、HC05蓝牙模块
1.1 HC-05简介
HC-05 蓝牙串口通信模块,是基于 Bluetooth Specification V2.0 带 EDR 蓝牙协议的
数传模块。无线工作频段为 2.4GHz ISM,调制方式是 GFSK。模块最大发射功率为 4dBm,
接收灵敏度-85dBm,板载 PCB 天线,可以实现 10 米距离通信。
模块采用邮票孔封装方式,模块大小 27mm×13mm×2mm,方便客户嵌入应用系统之
内,自带 LED 灯,可直观判断蓝牙的连接状态。
模块采用 CSR 的 BC417 芯片, 支持AT 指令,用户可根据需要更改角色(主、从模式)
以及串口波特率、设备名称等参数,使用灵活。
1.2 HC-05使用步骤
1.在拿到HC05蓝牙模块后,我们可以发现带底板的蓝牙模块具有6个引脚,具体请看下图。
经过测试发现,我们真正需要用到的只需要4个引脚,分别是VCC,GND,TXD,RXD。在拿到蓝牙模块之后,首先要测试的便是该蓝牙模块能否正常使用,所以就需要用串口调试助手测试。
首先将蓝牙模块和USB-TTL电平连接好后进行测试。因为是使用的串口,所以TXD和RXD要交叉连接。
当蓝牙模块上电后,指示灯会快闪,我们按下蓝牙模块上的按键,将KEY置为VCC,模块进入AT状态,便可以使用串口助手发送相应的AT指令,进行测试或者设置相应的参数。测试情况如下:
要注意,大多的时候发送AT指令时,需要按下按键KEY,才能发送成功。在检测完蓝牙模块和设定好参数以后,便可以将蓝牙模块运用到单片机上了。
蓝牙模块与单片机的连接及与手机的通信。
将蓝牙模块和单片机连接后,在手机未与蓝牙连接时,状态指示灯快闪,在手机与蓝牙连接后,状态指示灯两闪一停。这样,我们可以通过手机端的串口蓝牙调试助手 SPP蓝牙串口对蓝牙模块进行调试。
其中手机端、蓝牙模块、单片机的通信过程如下:在手机端发送相应的指令后,蓝牙模块HC05会将收到的指令通过串口发送给单片机,从而单片机就根据接收到的指令进行相应的动作。所以,我们在程序中只需要实现判断单片机通过串口接受的数据是什么就可以了。
既然是串口通信,所以最基本的便是STC8H1K16的串口初始化了,我用的是串口2(P10,P11),
void UART_config(void)
{
COMx_InitDefine COMx_InitStructure; //结构定义
COMx_InitStructure.UART_Mode = UART_8bit_BRTx; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
// COMx_InitStructure.UART_BRT_Use = BRT_Timer2; //选择波特率发生器, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2, 所以不用选择)
COMx_InitStructure.UART_BaudRate = 9600ul; //波特率, 110 ~ 115200
COMx_InitStructure.UART_RxEnable = ENABLE; //接收允许, ENABLE或DISABLE
COMx_InitStructure.UART_Interrupt = ENABLE; //中断允许, ENABLE或DISABLE
COMx_InitStructure.UART_Priority = Priority_0; //指定中断优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
COMx_InitStructure.UART_P_SW = UART2_SW_P10_P11; //切换端口, UART2_SW_P10_P11,UART2_SW_P46_P47
UART_Configuration(UART2, &COMx_InitStructure); //初始化串口2 UART1,UART2,UART3,UART4
// PrintString2("STC8 UART2 Test Programme!\r\n"); //UART2发送一个字符串
}
串口初始化后,便是主函数里要实现的功能了,我这里用蓝牙控制LED的点亮,代码很简单,就是判断串口2的接受缓存里的数据,如果是所要求的数据就执行,否则不执行。
void main(void)
{
GPIO_config();
UART_config();
EA = 1;
while (1)
{
if(RX2_Buffer[0]==0X31)
{
COM2.RX_Cnt=0;
LED2=1;
}
else if(RX2_Buffer[0]==0X32)
{
COM2.RX_Cnt=0;
LED3=1;
}
}
}
我这里插入一个数据手册的状态指示灯的说明吧。
这样,蓝牙模块的部分就要讲完了。
二、 PWM信号输出
1. PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域。
2.PWM重要参数: 频率 = 1 / TS 占空比 = TON / TS 精度 = 占空比变化步距
3.PWM占空比:是指一个周期内高电平时间和总时间的比值。
例如:PWM的周期为1ms,高电平时间为0.5ms,低电平时间为0.5ms,则频率就为1kHz,占空比就为百分之五十。
4.我们需要注意的是,在使用51单片机时,一般我们会用定时器来实现PWM功能,说白了PWM输出就是定时器的一个应用。这样我们就可以了解定时器产生PWM信号的结构。
我们可以看出,使用定时器的计数功能,定义一个用于计次的变量Counter和定义一个比较值Compare,在计次的值Counter不断增大时,我们可以不断改变Compare这一个比较值,实现PWM的脉冲宽度调制。
例如上图所示,当Compare的值为60时,当Counter的值不断增大时,当Counter的值小于比较值Compare时,其PWM输出低电平,当Counter的值大于比较值Compare时,其PWM输出高电平,则其占空比 TON / TS为40%。这样就实现了脉冲宽度调制。
注意:当Counter的值和比较值Compare的值比较时,具体是输出PWM的高电平还是输出PWM的低电平,是看我们的需求来的,具体可以去查询PWM的模式看它是怎样定义的。
tips:1.定时器是循环计数的,当定时器的计数值Counter的值达到定时器的装载值ARR后,则定时器进入下一个周期计数(这样设定的定时器装载值ARR应该就是定时器的定时周期。个人理解)。例如从上图可以看出它的装载值相当于100。可以用下面的图作为辅助分析。
所以我们可以说定时器的装载值ARR决定了其PWM的频率(也就是PWM的周期),比较值决定了其PWM的占空比。
三、 舵机控制
舵机的主要组成部分为伺服电机,所谓伺服就是服从信号的要求而动作。在信号来之前,转子停止不动;信号来到之后,转子立即运动。因此我们就可以给舵机输入不同的信号,来控制其旋转到不同的角度。
舵机接收的是PWM信号,当信号进入内部电路产生一个偏置电压,触发电机通过减速齿轮带动电位器移动,使电压差为零时,电机停转,从而达到伺服的效果。简单来说就是给舵机一个特定的PWM信号,舵机就可以旋转到指定的位置。
驱动原理:通过输出固定周期为20ms的PWM波形驱动舵机,其中正占空比(0.5~2.5ms)决定舵机的转动的角度为-90°到90°。下面这张图片可以帮助我们更好的理解。
我们需要注意的是,在给定好占空比之后,舵机旋转是转动到它应该到的指定角度,而不是转动多少度,比如说,给定1.5ms的高电平时间,其PWM占空比为1.5ms/20ms, 舵机就应该转到90度的位置,而不是旋转90度。
总的来说,要控制舵机的旋转,就通过定时器输出PWM信号,改变PWM占空比,就可以控制舵机旋转了。
思路分析:使用定时器产生一个20ms的实际脉冲,这个就是它的基准PWM周期,然后改变高电平时间控制舵机转动。
这样,我们就可以使用定时器输出的PWM波来控制舵机的转动了,实现PWM波输出的代码如下:
首先是定时器0的初始化:我们设置的定时长度是0.5ms,所以TL0=0x33,TH0=0xFE。这个定时长度可以在STC的软件里设置。
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x33; //设置定时初值
TH0 = 0xFE; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
在对定时器0初始化完成之后,便要写定时器0的中断服务函数了。代码如下:
/* 中断服务函数*/
void Timer0_Routine() interrupt 1
{
TL0 = 0x33;
TH0 = 0xFE; //设置定时长度,这个定时长度是0.5ms,定时器每0.5ms进入一次
Counter++; //定时器每进一次中断,则Counter的值加一
Counter%=40; //Counter每40次一个周期,这样PWM周期就是20ms
if(Counter<Compare) //计数值小于比较值
{
Motor=1; //输出1
}
else //计数值大于比较值
{
Motor=0; //输出0
}
}
以上代码的解释注释里都写得很清楚了,这样,一个20ms的基准周期就产生了,之后我们就可以用这个控制舵机旋转了。我们只要在主程序里实现我们的控制逻辑就好了。主函数里的程序如下:
void main(void)
{
int i=1;
GPIO_config();
UART_config();
Timer0_Init();
EA = 1;
while (1)
{
delay_ms(500);
LED9=1;
// if(i)
// {
// PWMB_Duty.PWM5_Duty++;
// }
// else
// {
// PWMB_Duty.PWM5_Duty--;
// }
// if(PWMB_Duty.PWM5_Duty>5000)
// {
// i=0;
// }
// if(PWMB_Duty.PWM5_Duty<=1000)
// {
// i=1;
// }
// EAXSFR();
// PWMB_CCR5 = PWMB_Duty.PWM5_Duty;
// EAXRAM();
if(RX2_Buffer[0]==0X31)
{
COM2.RX_Cnt=0;
Compare=1;
}
else if(RX2_Buffer[0]==0X32)
{
COM2.RX_Cnt=0;
Compare=3;
}
else if(RX2_Buffer[0]==0X33)
{
COM2.RX_Cnt=0;
Compare=5;
}
}
}
单片机通过接收手机向蓝牙模块发送过来的数据 ,改变Compare的值从而改变占空比,实现了控制舵机的旋转。这样,舵机的控制就算基本完成了。
总结
在做这个小项目时,遇到了很多关于STC8h1k16方面的问题,比如使用该单片机的定时器模块和PWM模块时,是不能控制舵机旋转的,原因是使用它的PWM模块时,它的最大PWM周期为65535,也就是大约6ms,无法达到20ms的时基脉冲。
/************************ PWM模式配置 ****************************/
//void PWM_config(void)
//{
// PWMx_InitDefine PWMx_InitStructure;
// PWMx_InitStructure.PWM5_Mode = CCMRn_PWM_MODE1; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
// PWMx_InitStructure.PWM6_Mode = CCMRn_PWM_MODE1; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
// PWMx_InitStructure.PWM7_Mode = CCMRn_PWM_MODE1; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
// PWMx_InitStructure.PWM8_Mode = CCMRn_PWM_MODE1; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
// PWMx_InitStructure.PWM5_SetPriority = Priority_0; //指定中断优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
// PWMx_InitStructure.PWM_Period = 65535; //周期时间, 0~65535
// PWMx_InitStructure.PWM5_Duty = PWMB_Duty.PWM5_Duty; // PWM5占空比时间, 0~Period
// PWMx_InitStructure.PWM6_Duty = PWMB_Duty.PWM6_Duty; // PWM6占空比时间, 0~Period
// PWMx_InitStructure.PWM7_Duty = PWMB_Duty.PWM7_Duty; // PWM7占空比时间, 0~Period
// PWMx_InitStructure.PWM8_Duty = PWMB_Duty.PWM8_Duty; // PWM8占空比时间, 0~Period
// PWMx_InitStructure.PWM_DeadTime = 0; //死区发生器设置, 0~255
// PWMx_InitStructure.PWM_EnoSelect = ENO5P | ENO6P; // ENO5P | ENO6P | ENO7P | ENO8P; //输出通道选择, ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
// PWMx_InitStructure.PWM_PS_SW = PWM5_SW_P17 | PWM6_SW_P21 | PWM7_SW_P02 | PWM8_SW_P03; //切换端口, PWM5_SW_P20,PWM5_SW_P17,PWM5_SW_P00,PWM5_SW_P74
// // PWM6_SW_P21,PWM6_SW_P54,PWM6_SW_P01,PWM6_SW_P75
// // PWM7_SW_P22,PWM7_SW_P33,PWM7_SW_P02,PWM7_SW_P76
// // PWM8_SW_P23,PWM8_SW_P34,PWM8_SW_P03,PWM8_SW_P77
// PWMx_InitStructure.PWM_CC5Enable = ENABLE; //开启PWM5P输入捕获/比较输出, ENABLE,DISABLE
// PWMx_InitStructure.PWM_CC6Enable = ENABLE; //开启PWM6P输入捕获/比较输出, ENABLE,DISABLE
// PWMx_InitStructure.PWM_CC7Enable = ENABLE; //开启PWM7P输入捕获/比较输出, ENABLE,DISABLE
// PWMx_InitStructure.PWM_CC8Enable = ENABLE; //开启PWM8P输入捕获/比较输出, ENABLE,DISABLE
// PWMx_InitStructure.PWM_MainOutEnable = ENABLE; //主输出使能, ENABLE,DISABLE
// PWMx_InitStructure.PWM_CEN_Enable = ENABLE; //使能计数器, ENABLE,DISABLE
// PWM_Configuration(PWMB, &PWMx_InitStructure); //初始化PWM, PWMA,PWMB
//}
//在这里不用PWM模式的原因是,PWM模块的最大周期为6秒
因为在这儿卡了很久,所以要指出来。其实用PWM模块无法实现时,我们就要用最基本的51定时器功能实现PWM输出,不能只在一颗树上吊死,因为51的很多方法都是可以通用的。
另外,还有很多定时器频率,定时时间的问题我还不是很清楚,我在后面应该会再进行研究,写下自己的心得体会,希望能和大家分享。也希望你们可以和我多讨论,共同进步。