目录
1 CubeMX基本配置(针对stm32F407ZGT6,时钟树的配置需要根据单片机的种类而定)
2.2 STM32F407ZG单片机连接USB转TTL(用于串口通信)
3.2 HAL_UART_Transmit()和HAL_UART_Receive()函数
1 CubeMX基本配置(针对stm32F407ZGT6,时钟树的配置需要根据单片机的种类而定)
2 接线
2.1 STM32F407ZG单片机连接ST_LINK
单片机 ST_LINK
9--------------------6
7--------------------2
20------------------3/4
1--------------------7/8
这里附上具体的接线图片:
2.2 STM32F407ZG单片机连接USB转TTL(用于串口通信)
单片机 USB转TTL
TX————RXD
RX————TXD
GND————GND
5V————5V
3 串口通信(附加中断+定时器+PWM综合)
3.1 printf/scanf的重定向
int fputc(int c,FILE *stream)//printf
{
uint8_t ch[1]={c};
HAL_UART_Transmit(&huart1,ch,1,0xFFFF);
return c;
}
int fgetc(FILE *stream)//scanf
{
uint8_t ch[1];
HAL_UART_Receive(&huart1,ch,1,0xFFFF);
return ch[0];
}
3.2 HAL_UART_Transmit()和HAL_UART_Receive()函数
HAL_UART_Transmit()是HAL库中的一个函数,用于向UART发送数据。该函数需要传入UART句柄、发送数据的缓冲区指针和发送数据的长度作为参数。在调用该函数时,UART会将缓冲区中的数据发送出去。如果发送成功,该函数会返回HAL_OK,否则会返回错误码。
参数1:使用的串口,2:要发送的数据,3:数据大小,4:发送的超时时间
HAL_UART_Transmit(&huart1,Data,sizeof(Data),1000);
HAL_UART_Receive()函数同理,不再赘述
注意:这种发送方式是阻塞式(阻塞模式就像是一个延时函数,当这个函数没处理完那么,所有的按照流程需要执行的代码都不会被执行,要等到这个延时完成,类似 平时看书上写的LED灯闪烁,用的delay()一样。而非阻塞模式就像他定义的那样,一般用的是中断,执行这条语句的时候,开启相应的中断达到一定的条件才进行处理,这样不会影响到流程的执行.我的理解就是,阻塞就是死等,非阻塞就是中断)
3.3 中断及中断回调函数
3.3.1 中断函数
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
HAL_UART_Receive_IT()函数同理
3.3.2 中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_IT(&huart1,buffer,100);
HAL_UART_Receive_IT(&huart1,buffer,100);
}
3.4 DMA
DMA,全称Direct Memory Access,即直接存储器访问。
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,
CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?
因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理,
DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。
HAL_UART_Receive_DMA(&huart1,buffer,100);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
注意:如果要想能够保证不止一次输入数据,需要在回调函数里写内容(放在it文件里)
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
HAL_UART_DMAStop(&huart1);
//len=100-__HAL_DMA_GET_COUNTER(huart1.hdmarx);
//rintf("%d\r\n",len);*/
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
3.5 综合基础知识,通过串口通信控制
实现功能如下:
1.LED灯的亮灭
2.定时器中断控制LED灯按频率闪烁
3.PWM波控制呼吸灯
4.PWM波控制舵机转动
5.按键控制灯的亮灭
6.高电平控制蜂鸣器(注意蜂鸣器声音挺大的,第一次用的时候吓一跳)
注意事项:
1.头文件加一个stdio.h,因为用到了printf和scanf,并且需要重定向(这两个函数原本是在显示屏上进行的)
2.最好定义全局变量,防止值的改变
3.利用中断控制时在CubeMX配置时要注意打开global interrupt选项
4.我的单片机PF9和PF10引脚对应两个LED灯
5.PWM波在Keil编写程序时要加上Start函数打开PWM波
6.我加的printf(check)是用来检验程序是否正常运行
7.在使用PWM波控制呼吸灯时要看好灯对应的那个引脚连接了哪个定时器
8.在使用PWM波控制舵机时要看好自己开的定时器对应哪个引脚,插信号线要插对
9.有关舵机的具体计算在下面有,可以先看下面的再回来看
10.输入数据到buffer【0】的位置来控制程序
11.如果不加DMA_Stop()函数则后续输入的数据会累积到buffer【1】【2】等等,这样if判断句就没用了
12.windows换行的话需要同时写\r\n
13.按键和亮灯原理:(我的单片机KEY1对应PE3,KEY0对应PE4)
14.按键达不到预期效果(PF9定时器占用了),意会即可
这样算是用到了所有stm32的基础知识,包括:
1.GPIO控制高低电平
2.定时器中断
3.PWM波控制
4.串口通信(阻塞、非阻塞、DMA)
CubeMX整体配置:
代码:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2023 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
uint8_t buffer[100];
uint8_t len;
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int c,FILE *stream)//printf
{
uint8_t ch[1]={c};
HAL_UART_Transmit(&huart1,ch,1,0xFFFF);
return c;
}
int fgetc(FILE *stream)//scanf
{
uint8_t ch[1];
HAL_UART_Receive(&huart1,ch,1,0xFFFF);
return ch[0];
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_DMA(&huart1,buffer,100);
HAL_UART_Receive_DMA(&huart1,buffer,100);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_TIM14_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim14,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//HAL_UART_Transmit(&huart1,"HELLO",5,0xFFFF);
//uint8_t buffer[100];
HAL_UART_Receive_DMA(&huart1,buffer,100);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
//printf("check0\r\n");
printf("%d\r\n",buffer[0]);
HAL_Delay(1000);
if(buffer[0]==1)
{
printf("check1\r\n");
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
HAL_Delay(500);
printf("check2\r\n");
HAL_Delay(1000);
//memset(buffer,0,sizeof(buffer));
}
if(buffer[0]==2)
{
printf("check3\r\n");
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
HAL_Delay(500);
printf("check4\r\n");
HAL_Delay(1000);
//memset(buffer,0,sizeof(buffer));
}
if(buffer[0]==3)
{
printf("check5\r\n");
for(uint16_t cnt=0;cnt<1000;cnt++)
{
__HAL_TIM_SetCompare(&htim14,TIM_CHANNEL_1,cnt);
HAL_Delay(1);
}
for(uint16_t cnt=1000;cnt>0;cnt--)
{
__HAL_TIM_SetCompare(&htim14,TIM_CHANNEL_1,cnt);
HAL_Delay(1);
}
printf("check6\r\n");
HAL_Delay(1000);
}
if(buffer[0]==4)
{
printf("check7\r\n");
HAL_TIM_Base_Start_IT(&htim2);
printf("check8\r\n");
HAL_Delay(1000);
}
if(buffer[0]==5)
{
printf("check9\r\n");
for(uint16_t cnt=500;cnt<=2500;cnt++)
{
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,cnt);
HAL_Delay(1);
}
for(uint16_t cnt=2500;cnt>=500;cnt--)
{
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,cnt);
HAL_Delay(1);
}
printf("check10\r\n");
HAL_Delay(1000);
}
if(buffer[0]==6)
{
printf("check11\r\n");
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==RESET)
{
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
printf("check12\r\n");
}
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==SET)
{
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
printf("check13\r\n");
}
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==RESET)
{
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
printf("check14\r\n");
}
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==SET)
{
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
printf("check15\r\n");
}
printf("check16\r\n");
HAL_Delay(1000);
}
if(buffer[0]==7) )
{
printf("check17\r\n");
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_8,GPIO_PIN_SET);
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_Delay(500);
printf("check18\r\n");
}
/*if(buffer[0]!='\0')
{
printf("%s\r\n",buffer);
memset(buffer,0,sizeof(buffer));
printf("%d\r\n",len);
}*/
HAL_Delay(100);
/*uint8_t SendBuffer[30]={"hello,world!"};
HAL_UART_Transmit(&huart2, (uint8_t *)SendBuffer, sizeof(SendBuffer), HAL_MAX_DELAY);*/
}
/* USER CODE END 3 */
}
4 舵机
4.1 相关计算
我们打开TIM3定时器,这个定时器是挂在APB1总线上的,根据配置好的时钟树可知
频率是84MHz
然后为了方便计算,我们把分频系数设置成84-1,这样相当于1秒钟点1000000次
由于舵机的驱动信号是50Hz,周期是0.02s,所以在一个周期内会点20000次
舵机的转动角度是按照高电平的占空比来计算的
经过手算,我们可以大致推出角度和比较值之间的关系,舵机的难点也就在这了
4.2 接线
舵机 单片机
信号线(黄)-------PA6(根据自己的定时器而定)
电源线(红)-------ST-LINK上的5V(9/10)
地线(棕)----------GND
5 PID控制算法
5.1 简介
PID控制中有P、I、D三个参数,PID即:Proportional(比例)、Integral(积分)、Differential(微分)的缩写。只有明白这三个参数的含义和作用才能完成控制器PID参数整定,让控制器到达最佳控制效果。
PID是经典的闭环控制算法,具有原理简单,易于实现,适用面广,控制参数相互独立,参数的选定比较简单等优点。
凡是需要将某一个物理量“保持稳定”的场合(比如维持平衡,稳定温度、转速等),PID都会派上大用场。
5.2 比例Proportional
比例控制器实际上就是个放大倍数可调的放大器,即△P=Kp×e,式中Kp为比例增益,即Kp可大于1,也可小于1;e为控制器的输入,也就是测量值与给定值之差,又称为偏差。
比例控制有个缺点,就是会产生余差,要克服余差就必须引入积分作用。
只有比例系数无法准确到达目标位置,但增大比例系数有利于减小误差
5.3 积分Integral
控制器的积分作用就是为了消除自控系统的余差而设置的。所谓积分,就是随时间进行累积的意思,即当有偏差输入e存在时,积分控制器就要将偏差随时间不断累积起来,也就是积分累积的快慢与偏差e的大小和积分速度成正比。只要有偏差e存在,积分控制器的输出就要改变,也就是说积分总是起作用的,只有偏差不存在时,积分才会停止。
实际上积分作用很少单独使用,通常与比例作用一起使用,使其既具有把偏差放大(或缩小)的比例作用,又具有将偏差随时间累积的积分作用,且其作用方向是一致的。这时控制器的输出为:△P=Ke+△Pi,式中△P为控制器输出值的变化;Ke为比例作用引起的输出;△Pi为积分作用引起的输出。
同时使用比例系数和积分可以准确到达目标位置,但当比例系数过大时会出现不稳定性
5.4 微分Differential
微分作用主要是用来克服被控对象的滞后,常用于温度控制系统。除采用微分作用外,在使用控制系统时要注意测量传送的滞后问题,如温度测量元件的选择和安装位置等。
在常规PID控制器中,微分作用的输出变化与微分时间和偏差变化的速度成比例,而与偏差的大小无关,偏差变化的速度越大,微分时间越长,则微分作用的输出变化越大。但如果微分作用过强,则可能由于变化太快而由其自身引起振荡,使控制器输出中产生明显的“尖峰”或“突跳”。为了避免这一扰动,在PID调节器和DCS中可使用微分先行PID运算规律,即只对测量值PV进行微分,当人工改变控制器的给定值SP时,不会造成控制器输出的突变,避免了改变SP的瞬间给控制系统带来的扰动。如TDC-3000,则在常规PID算法中增加一个软开关,组态时供用户选择控制器对偏差、还是测量值进行微分。
微分保证系统能够平稳运行,不至于出现太大的波动
5.5 公式
连续形式:
离散形式(常用,由连续形式推导):
5.6 积分限幅
试想一下,使用P和I项进行控制,无人机在地面准备起飞,期望高度是100m。此时一个人摁住无人机,那么误差值一直为100m,控制器P项输出保持不变,控制器I项输出是线性增长的
一段时间后,那个人突然不摁了,此时控制器I项输出可能非常大,再加上控制器P项输出,控制器给电机的输出非常大,那么无人机速度就非常快并且飞的非常高。对于这种情况,我们对积分限幅,I项增加到一定值时就不再增了。那么无人机在上面这种情况下就不至于飞的很快很高,这就是积分限幅的作用。
5.7 积分限行
我们直接将传感器的反馈值分出来一路给到控制器的D项,这样做有一个好处就是如果希望无人机快速反应移动,当期望高度突变的时候,影响会先给到P和I项,由于开始时无人机位置没有突变,传感器反馈的实际高度值也不会突变,因此不会马上给到D项,这样就有效减小了D项开始的缓冲作用,使无人机可以快速反应移动。如果不这样做,那么如果期望高度突变,D项的微分值也会突变变得很大,缓冲作用非常大,使相应效果大大降低。
5.8 相关代码
chassis_motor_pid[i].SpeedPID.current=motor_chassis[i].speed_rpm;
pid_calc(&chassis_motor_pid[i].SpeedPID);
void pid_calc(_pid* pid)
{
pid->e=pid->target-pid->current;
pid->p_out=(int32_t)(pid->Kp*pid->e);
if(fabs(pid->e)<I_Band)//积分限幅和积分分离
{
pid->i_out+=(int32_t)(pid->Ki*pid->e);
limit(&(pid->i_out),pid->IntegralLimit);
}
else
{
pid->i_out=0;
}
pid->d_out=(int32_t)(pid->Kd*(pid->e-pid->last_e));
pid->total_out=pid->p_out+pid->i_out+pid->d_out;//公式
limit(&(pid->total_out),pid->MaxOutput);//输出限制
pid->last_e=pid->e;//更新last值
}
6 systick(定时器,多用于延时)
6.1 寄存器
Systick的寄存器分为:
CTRL
LOAD
VAL
CALIB
ENABLE位就是开关Systick
TICKINT的作用是当写入的值为"1"时,会进入SysTick_Handler()中断服务函数,随后执行用户代码。
CLKSOURCE该位要联合最上方的图来理解,当值为0时Systick的时钟频率为AHB/8也就是72MHz/8=9MHz,当值为1时Systick的时钟频率为AHB桥的频率。AHB桥的频率常用为72MHz。
COUNTFLAG该位的作用就是告诉使用者(芯片程序)Systick已经计数完成,用户程序可以通过检测该位是否为"1"来判断Systick的计数状态。
RELOAD该位是一个24位的寄存器,也就是说最大支持0xFFFFFF,同时还是能够自动重装载值,自动重装载的理解就是当RELOAD倒数至0后,能够重新将用户预设值填充,比如说预设值为0xFF,那么当倒数至0时,它能够自动将0xFF填充进RELOAD。
CURRENT该位是与RELOAD相对应的,它的值就等于RELOAD的值,只不过当我们对该寄存器进行写操作时,会强行将RELOAD的值清0,同时会将CTRL->COUNTFLAG的标志位置0。读操作则只是返回RELOAD的值。
6.2 代码
static uint8_t us = 0;
static uint16_t ms = 0;
void SysTickConfig(void)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
us = SystemCoreClock / 8000000;
ms = (uint16_t )us *1000;
}
void delay_us(uint32_t nus)
{
uint32_t flags;
SysTick->LOAD = nus * us;
SysTick->VAL = 0x00;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
do
{
flags = SysTick->CTRL;
}while((flags&0x01)&&!(flags&(1<<16)));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0x00;
}
void delay_ms(uint16_t nms)
{
uint32_t flags;
SysTick->LOAD = (uint32_t )nms * ms;
SysTick->VAL = 0x00;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
do
{
flags = SysTick->CTRL;
}while((flags&0x01)&&!(flags&(1<<16)));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0x00;
}
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8)
配置CTRL->CLKSOURCE,将其选择为外部时钟源。
us = SystemCoreClock / 8000000
这里是整个Systick的重点,定时器准不准就看这里的参数,SystemCoreClock 是系统时钟频率,这里取72MHz,同时又由于上方代码进行了一个8分频,所以需要除以8。但如果单单只是/8,那么此时一个us应该为1s,而Systick的计数速度是由时钟频率决定的,此时时钟频率为9MHz,代表计数一秒需要数9M个数,而1s中有1M个us,所以用9M/1M就等于Systick计时1us需要数的数,完整的式子应该是 us = (72,000,000 / 8) / 1,000,000=9
ms = (uint16_t )us *1000
这里不难理解,1ms就是等于1000us,前面加个强制类型转换是为了让数据类型匹配上,避免编译器优化,造成数据丢失。
SysTick->LOAD = nus * us
此处就是给LOAD->RELOAD填充计数值自动重装载值,切记此处最大不超过24位。
SysTick->VAL = 0x00
这里是为了清空计数器,确保计数精准。
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk
启动Systick。
do
{
flags = SysTick->CTRL;
}while((flags&0x01)&&!(flags&(1<<16)));
此处作用是等待计数完成,flags&(1<<16)这里就是检测计数完成COUNTFLAG标志位是否为'1'
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk
关闭Systick。
SysTick->VAL = 0x00
再次清空计数器。
下方的delay_ms()的原理同上方一样,只不过是将SysTick->LOAD = nus * us 的us换成ms,SysTick->LOAD = nms * ms ,当使用delay_ms()时,最大延时建议不超过1500ms
7 蓝牙模块
CH340模块(USB转TTL) HC-05模块
VCC(5V) —————————— VCC
GND —————————— GND
RXD —————————— TXD
TXD —————————— RXD
最开始,用USB转TTL连接HC-05模块(注意此时需要先按住HC-05模块上面的按钮再上电),这时会进入AT模式(此时的灯应该是间隔比较长时间才灭一次),然后打开串口调试助手,波特率设为38400,接下来进行初始化操作
按照步骤
配置蓝牙模块基本信息
AT+NAME=HC-05 修改蓝牙模块名称为HC-05
AT+ROLE=0 蓝牙模式为从模式
AT+CMODE=1 蓝牙连接模式为任意地址连接模式,也就是说 该模块可以被任意蓝牙设备连接
AT+PSWD=1234 蓝牙配对密码为1234
AT+UART=9600,0,0 蓝牙通信串口波特率为9600,停止位1位, 无校验位
我们直接把蓝牙模块插到原来USB转TTL的位置上(蓝牙模块的用处就是可以无线调试而不用电脑上的虚拟串口调试) (注意不要按按钮)
我们在手机上安装蓝牙串口调试软件,并配对(注意在这之前手机的设置->蓝牙里已经连接成功)
然后这时会发现灯处于长灭状态(偶尔亮一会),就可以正常使用了,我们在手机里输入数据,跟原来用电脑的虚拟串口一模一样