stm32项目_stm32f103c8t6项目_循迹避障小车完整制作过程_智能小车设计_STM32智能小车教程-循迹-避障-蓝牙遥控-跟随
[硬件]
元件选型
照片上传出问题了,改天补上,着急的可以看视频
视频链接在这里
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sKjJGMXv-1646313963955)(…/…/…/…/MyBlogGitee/blog/source/imagesSTM32%E5%B0%8F%E8%BD%A6%E7%AC%94%E8%AE%B0V1.0/image-20220303131457462.png)]
原理图绘制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Y1vYGWJ-1646313963957)(…/…/…/…/MyBlogGitee/blog/source/imagesSTM32%E5%B0%8F%E8%BD%A6%E7%AC%94%E8%AE%B0V1.0/image-20220303131554546.png)]
要结合购买的元件模块设计原理图
比如
查看数据手册与参考手册确定引脚功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDAUGJ9n-1646313963957)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220208194421919.png)]
PCB布局与走线
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnDQ4kBN-1646313963958)(…/…/…/…/MyBlogGitee/blog/source/imagesSTM32%E5%B0%8F%E8%BD%A6%E7%AC%94%E8%AE%B0V1.0/image-20220303132333282.png)]
电源线走线粗一点
可以把电源线走在底层,信号线在顶层
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ch7UqHRV-1646313963959)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220208210628764.png)]
根据元件特点布局
核心板的排母间距要注意!!!
比如:这种元件就要放到PCB边上
下单PCB打样
检查DRC没有问题就可以打样了
焊接PCB
焊接比较简单
如果大家有问题,留言我抽空补上视频
安装组装
安装比较简单
如果大家有问题,留言我抽空补上视频
[软件]编程开发中如何获得资料
模块资料
我们通过淘宝获得
STM32F103C8T6最小系统板模块
通过淘宝下载同一型号资料即可
其他模块资料
可以通过淘宝简介得到
STM32外设驱动资料
我们通过正点原子下载获取:
小车原理图
通过EDA软件导出
程序移植-STM32F103ZET6移植到STM32F103C8T6
第一步
打开魔术棒,点击Device,更改芯片类型为C8T6
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3ALbwms-1646313963959)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113173242935.png)]
第二步
点击Target,晶振频率改为8Mhz
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJJFUsmS-1646313963960)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113173436660.png)]
第三步
点击C/C++,将define中的STM32F10X_HD,USE_STDPERIPH_DRIVER改成STM32F10X_MD,USE_STDPERIPH_DRIVER
STM32F10X_MD,USE_STDPERIPH_DRIVER
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMLZGuGq-1646313963960)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113173653028.png)]
第四步
点击Utilities,点开settings,在Flash Download栏下,将STM32F103ZET6中512k的移除,并改为128k,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2jzyDeIb-1646313963961)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113174002136.png)]
第五步
将该工程文件中CORE中的startup_stm32f10x_hd.s文件换为startup_stm32f10x_md.s文件
- 删除原来的:startup_stm32f10x_hd.s
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BbjxCZc-1646313963961)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113183940843.png)]
- 将startup_stm32f10x_md.s复制到工程文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdU25Kou-1646313963962)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113184742797.png)]
- 工程中添加startup_stm32f10x_md.s
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DILgDGz8-1646313963962)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113192246006.png)]
第六步
编译一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oRbc6YlL-1646313963963)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113192519150.png)]
那么我们就完成了把ZET6的工程移植成为C8T6的工作,下面让我们点灯测试一下啊.
使用STlink烧录 时候出现:
方法:
GPIO输出实验点亮C8T6板载小灯
第一步
查阅原理图,小灯接在PC13上下面驱动PC13
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jeFGlgSG-1646313963963)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195307685.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FUvVvhyX-1646313963963)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195226345.png)]
思考题:如果同时驱动PC13与PC14,应该如何编写?(答案:应该增加下图代码)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9NIgtkw-1646313963964)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195611097.png)]
LED_Init()函数的代码
void LED_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能PB,PC端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PC13GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHzGPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOC.13GPIO_SetBits(GPIOC,GPIO_Pin_13); //PC.13输出高
}
LED.h 部分宏定义
#define LED PCout(13)// PC13
第二步
编译下载(如果没有运行,需要按复位 运行)
以上我们就完成基本测试,下面让我们学习一下,如何从零设计小车!!!<( ̄︶ ̄)↗[GO!]
小车设计
总体设计方案
总体的设计方案对完成项目非常重要,下面是小车的设计方案,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJgjG0yf-1646313963964)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113215641227.png)]
对小车的模组进行了,简单分类。这里简单介绍一下:
一辆’自动化’小车,要能够像人一样,有观察事物的眼睛,有处理事情的大脑,有可以跑动的腿,这里:
输入信号模块就像人类的眼睛,可以讲一些外界信息测量并送至’大脑’,比如超声波把距离信息发送给单片机。
执行模块就像人类的腿,可以根据’大脑’控制指令进行’运动’,比如舵机根据单片机指令旋转。
单片机就像人类的大脑,可以根据输入信号模块完成对执行模块的控制。
电源负责给整个系统供电。
OLED模块显示一些系统信息。
[硬件]系统硬件设计
主控:STM32单片机
使用:STM32f103c8t6最小系统板
选择原因:STM32F103C8T6价格较低,资源丰富可以满足项目要求,可以在其数据手册阅读资源介绍。
注意:
系统需要5V供电,可输出3.3V
OLED模块
使用:OLED显示屏模块 0.96寸 IIC/SPI
选择原因:价格较低、使用方便
注意:
这里使用 四管脚 顺序为 GND VCC SCL SDA,绘制PCB要注意顺序
供电为3.3V
陀螺仪
使用:MPU-6050模块 三轴加速度陀螺仪6DOF GY-521
原因:满足项目需要,使用方便
注意:
供电3V-5V
超声波测距模块
使用:HC-SR04 超声波测距模块
注意:
绘制PCB注意四个引脚顺序 Vcc Trig Echo Gnd
供电3.3V-5V
测距原理
不同模式
GPIO模式
红外循迹模块
使用:寻迹传感器 TCRT5000红外反射传感器
注意:
供电3.3V-5V
引脚顺序为: VCC GDN DO AO (DO表示数字输出,AO表示模拟输出)
来自TB的介绍
不完全总结就是:红外对管前面是黑色的时候,DO引脚为高电平,二极管熄灭状态。前面是红色的时候为低电平,二极管点亮。
蓝牙模块
使用:HC-05 主从机一体蓝牙串口透传模块
注意:
供电3.6V-6V
引脚顺序 VCC GND TXD RXD
按键
使用:这里按键使用PCB 元件
电机驱动
使用:TB6612FNG电机驱动模块
注意:
供电 比较复杂
来自淘宝的介绍
电机
使用:电机马达 DC3V-6V直流减速电机
注意:
供电3V-6V
电机要能够安装在小车车架上(这里使用的电机是小车车架套餐配套的)
舵机
使用:SG90 9g舵机 固定翼航模遥控飞机 180度舵机
注意:
供电4.8V-6V
需要控制角度,故购买180度 舵机
电源
使用:12v锂电池组18650充电带保护板大容量电瓶通用移动电源便携蓄电池
注意:
使用电池输出为12V
接口为DC5.5-2.1公母头
系统软件设计
点亮小灯
查看原理图
查阅原理图,小灯接在PC13上下面驱动PC13
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNU6Z0Mb-1646313963965)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195307685.png)]
编写驱动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-60KaVxby-1646313963966)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195226345.png)]
思考题:如果同时驱动PC13与PC14,应该如何编写?(答案:应该增加下图代码)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRdMp3nk-1646313963966)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220113195611097.png)]
LED_Init()函数的代码
void LED_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能PB,PC端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PC13GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHzGPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOC.13GPIO_SetBits(GPIOC,GPIO_Pin_13); //PC.13输出高
}
LED.h 部分宏定义
#define LED PCout(13)// PC13
测试
编译下载(如果没有运行,需要按复位 运行)
电机驱动
由TB6612介绍得,通过控制AO和AO2高低电平可以控制AIN1和AIN2输出。
GPIO 高低电平控制AIN和BIN
- 查阅原理图AIN1、AIN2、BIN1、BIN2依次接在单片机的PB13、PB12、PB1、PB0
原理同GPIO输出高低电平见第二节
TB6612 GPIO驱动函数代码
//驱动6612 的AIN1 AIN2 BIN1 BIN2 // AIN1 PB13 // AIN2 PB12 // BIN1 PB1 // BIN2 PB0 void TB6612_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口时钟 GPIO_InitStructure.GPIO_Pin =GPIO_Pin_13 |GPIO_Pin_12|GPIO_Pin_0|GPIO_Pin_1; //PB0 OB1 PB12 PB13端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHzGPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIO_SetBits(GPIOB,GPIO_Pin_13 |GPIO_Pin_12|GPIO_Pin_0|GPIO_Pin_1); //PB0 OB1 PB12 PB1 输出高}
相关宏定义
#define AIN1 PBout(13)// PB13 #define AIN2 PBout(12)// PB12 #define BIN1 PBout(1)// PB1 #define BIN2 PBout(0)// PB0
PWM控制PWMA和PWMB
将 PWM输出实验 的 timer 文件移植到我们前面点灯的工程中,更改驱动文件
查看原理图 PWMA 和PWMB依次连接PA11和PA8
查看 参考手册 关于定时器复用功能重映射的介绍(中文参考手册第119页)
初始化外设
配置对应引脚功能
初始化TIM1
初始化TIM1 相应通道的 PWM模式
使能
注意输出使能 高级定时器必须使用:TIM_CtrlPWMOutputs(TIM_TypeDef TIMx, FunctionalState NewState);*
//TIM1 PWM部分初始化 //PWM输出初始化 //arr:自动重装值 //psc:时钟预分频数 void TIM1_PWM_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;//使能对应定时器RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //使能定时器1时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM1, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5 //配置对应引脚功能//设置该引脚为复用输出功能,输出TIM1 CH1 和CH4GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11; //TIM_CH1 TIM_CH4 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO//初始化TIM1TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位//初始化TIM1 Channel1 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器//初始化TIM1 Channel4 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OC4Init(TIM1, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器TIM_Cmd(TIM1, ENABLE); //使能TIM1TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能,高级定时器必须开启这个}
调用初始化函数、改变占空比。
TIM1_PWM_Init(1999,359); //TIM1挂在APB2为72M ,故计算 72 000 000 /(359+1)/(1999+1) = 100 Hz,//故设置了频率为100 Hz、自动重装载值 1999TIM_SetCompare1(TIM1,100); //设置 TIM1 通道1 捕获/比较寄存器值 为 1000 可以计算出占空比//PA8 PWMBTIM_SetCompare4(TIM1,1900); //设置 //PA11 PWMA
通过软件仿真
逻辑分析仪观察波形输出、显示PWM波形
设置好仿真环境
打开逻辑分析仪
添加要观察的引脚
跳到设置对应程序位置,打开仿真
打开实时更新选项
调节观察分析仪
产生的如图方波就是一种PWM波
那么在程序哪里设置的这些参数那
时钟预分频数 决定了PWM 频率和周期
TIM1_PWM_Init(1999,359); //TIM1挂在APB2为72M ,故计算 72 000 000 /(359+1)/(1999+1) = 100 Hz,
那么谁调节占空比那?
非常好理解、定时器的计数器向上计数就是越来越大。
PWM 模式我们可以看手册
3.这里的TIM_OCPolarity_High 就是把有效电平设置为高
举个栗子:如果我们设置上面的示例参数,工作过程应该是怎么的呐?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EVjjm9v6-1646313963967)(C:\Users\z1930\AppData\Roaming\Typora\typora-user-images\image-20220201141825559.png)]
电机控制通过AIN1、AIN2、BIN1、BIN2控制电机正反转,通过PWMA、PWMB控制电机转速。
AIN1 = 1;AIN2 = 0;BIN1 = 1;BIN2 = 0;TIM_SetCompare4(TIM1,1500); //设置 ATIM_SetCompare1(TIM1,1500); //设置B
让小车跑一跑吧
小车电机线正确接法
错误接法
小车直行
void Forward(void)
{AIN1 = 1;AIN2 = 0;BIN1 = 1;BIN2 = 0;TIM_SetCompare4(TIM1,1500); //设置 ATIM_SetCompare1(TIM1,1500); //设置B
}
小车后退
void Backward(void)
{AIN1 = 0;AIN2 = 1;BIN1 = 0;BIN2 = 1;TIM_SetCompare4(TIM1,1500); //设置 ATIM_SetCompare1(TIM1,1500); //设置B
}
小车左转
void Leftward(void)
{AIN1 = 0;AIN2 = 1;BIN1 = 1;BIN2 = 0;TIM_SetCompare4(TIM1,1500); //设置 ATIM_SetCompare1(TIM1,1500); //设置B }
小车右转
void Rightward(void)
{AIN1 = 1;AIN2 = 0;BIN1 = 0;BIN2 = 1;TIM_SetCompare4(TIM1,1500); //设置 ATIM_SetCompare1(TIM1,1500); //设置B}
舵机控制
查看原理图
芯片手册
使用上节移植的定时器三例程
不需要开启部分重映射,
//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
初始化函数为
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{ GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;//使能对应时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5 //设置该引脚为复用输出功能,输出TIM3 CH1的PWM脉冲波形 GPIOA.6GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM_CH1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO//初始化TIM1TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位//初始化TIM3 Channel1 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OC1Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC1TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR1上的预装载寄存器TIM_Cmd(TIM3, ENABLE); //使能TIM3}
让舵机摇摇头
调用初始化函数和改变占空比
TIM3_PWM_Init(999,1439); //时钟源为72MHZ 故72 000 000 /(1439+1)/(999+1)=50HZTIM_SetCompare1(TIM3,32); //舵机向右delay_ms(900);TIM_SetCompare1(TIM3,80); //舵机向前delay_ms(900);TIM_SetCompare1(TIM3,130); //舵机向左 delay_ms(900);
然后
按键与红外对管
按键外部中断实验
让我们先实现按键控制灯的亮灭
查看原理图
这里发现翻车,呜呜呜
由于C8T6小板子的PA12接了上拉电阻,所以使用PA12的时候要注意。而且如果我们用Mrico USB供电可能会影响PA11。
现在我们的原理图是这样的 KEY1-PA7 KEY2-PA12
配置按键端口模式
通过原理图知:KEY1(PA7)应该配置成下拉输入、上升沿触发。
KEY2(PA12)应该配置成上拉输入、下降沿触发。
//按键初始化函数
void KEY_Init(void) //IO初始化
{ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA7设置成输入,默认下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.7GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.12}
配置中断线和配置外部通道
//KEY外部中断服务程序
void KEY_EXTIX_Init(void)
{EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;KEY_Init(); // 按键端口初始化RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟//GPIOA.7 中断线以及中断初始化配置 上升沿触发 PA7 KEY_1GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource7); //选择GPIO引脚作为中断线EXTI_InitStructure.EXTI_Line=EXTI_Line7; //线路选择 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //事件选择 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //触发模式 上升沿触发EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器//GPIOA.5 中断线以及中断初始化配置 下降沿触发PA12 KEY_2GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource12);EXTI_InitStructure.EXTI_Line=EXTI_Line12;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //使能按键KEY1所在的外部中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //使能按键KEY0所在的外部中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
相关宏定义 读取按键状态
#define KEY_1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)//读取按键KEY_1
#define KEY_2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12)//读取按键KEY_2
编写响应中断函数
void EXTI9_5_IRQHandler(void)//按键KEY_1 和KEY_2的中断服务函数
{delay_ms(10);//消抖if(KEY_1 == 1) //判断按键KEY_1 是否被按下{LED =! LED;EXTI_ClearITPendingBit(EXTI_Line7); //清除LINE7上的中断标志位 }}
void EXTI15_10_IRQHandler(void)//按键KEY_SW1 和KEY_SW2的中断服务函数
{delay_ms(10);//消抖if(KEY_2 == 0) //判断按键KEY_2 是否被按下{LED =! LED;EXTI_ClearITPendingBit(EXTI_Line12); //清除LINE7上的中断标志位 }
}
调用初始化函数
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
//如果没有设置中断优先级分组要先设置
KEY_EXTIX_Init(); //初始化外部中断输入
烧录调试
观察现象
红外对管硬件使用方法
详见:系统硬件设计->红外循迹模块
可以把红外对管看成’按键’,当前面有黑色时候为高电平,前面白色低电平。
红外对管的驱动
红外对管这里使用查询的方式,通过GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)函数获得对应端口的电平
查看原理图 红外对管依次连接 PB5 、PB4 、PB3 、PA15
红外管GPIO初始化
注意:这里我们需要使用的PB3、PB4、PA15是单片机的’特殊引脚
我们打开数据手册:STM32F103x8B_DS_CH_V10,在引脚定义章节,说明了复位后的主功能和默认复用功能以及重定义功能。
在参考手册:STM32中文参考手册_V10, 在8.3.5 JTAG/SWD复用功能重映射中,说明了引脚使用
所以我们需要关闭JTAG-DP 启用SW-DP ,我们重映射配置应写为GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
//红外循迹TCRT5000初始化函数
void TCRT5000_Init(void) //IO初始化
{ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);//使能PORTA,PORTB时钟GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//重映射配置关闭JTAG-DP 启用SW-DP从而可以使用PA15 PB3 PB4GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA15 设置成下拉输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.15GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_4|GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 设置成下拉输入GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB 5 4 3
}
一些宏定义,利用函数读取电平
#define HW_1 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)//读取 PB5 电平
#define HW_2 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_4)//读取 PB4
#define HW_3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_3)//读取 PB3
#define HW_4 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)//读取 PA15
调用初始化函数
TCRT5000_Init();
红外对管控制小灯
while(1){if(HW_1 == 1 && HW_2==0 && HW_3 == 1 && HW_4 == 0 )//当第一个和第三个前面是黑色时候板子小灯亮,其他情况板子小灯灭{ LED =0;}else{ LED =1;}}
练一练–红外对管循迹
while(1){if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0){Forward();delay_ms(50);}if(HW_1 == 0 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0){Rightward();delay_ms(150);}if(HW_1 == 1 && HW_2 == 0 && HW_3 == 0 && HW_4 == 0){Rightward();delay_ms(250);}if(HW_1 == 1 && HW_2 == 1 && HW_3 == 0 && HW_4 == 0){Rightward();delay_ms(300);}if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 0){Leftward();delay_ms(150);}if(HW_1 == 0 && HW_2 == 0 && HW_3 == 0 && HW_4 == 1){Leftward();delay_ms(250);}if(HW_1 == 0 && HW_2 == 0 && HW_3 == 1 && HW_4 == 1){Leftward();delay_ms(300);}}
串口接收发送
STM32串口初始化
这里先初始化使用串口1
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记 void uart_init(u32 bound){//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟//USART1_TX GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART1_RX GPIOA.10初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 //Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式USART_Init(USART1, &USART_InitStructure); //初始化串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(USART1, ENABLE); //使能串口1 }
在main中定义标志位
int g_USART1_FLAG1 = 0; //串口控制标志位
在usart.h中声明变量
extern int g_USART1_FLAG1 ;
在中断服务函数添加处理
void USART1_IRQHandler(void) //串口1中断服务程序{u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.OSIntEnter();
#endifif(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾){Res =USART_ReceiveData(USART1); //读取接收到的数据if(Res == 'A') g_USART1_FLAG1 = 1 ; //根据接受的数据 置为标志位if(Res == 'B')g_USART1_FLAG1 = 2 ;if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始else USART_RX_STA|=0x8000; //接收完成了 }else //还没收到0X0D{ if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } }} }
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.OSIntExit();
#endif
}
调用初始化函数
uart_init(115200); //串口初始化为115200
在main.c 的逻辑
while(1){ //串口 if(g_USART1_FLAG1 == 1){ LED =! LED; }if(g_USART3_FLAG1 == 2) {LED =! LED; } }
测试单片机串口
TTL与单片机连接
TTL插入电脑,使用串口助手->选择端口->更改波特率115200->发送数据
现象 发送A 或B 可以使小灯反转、发送其他命令无现象。
配置蓝牙
更改蓝牙波特率
见硬件蓝牙介绍
我们在AT模式下设置发送AT指令:AT+UART=115200,0,0
测试蓝牙
断电重启蓝牙,更改软件波特率为115200,打开手机蓝牙与HC-05配对 (密码:1234)
使用蓝牙调试器(应用商店下载即可),发送aa 观察电脑串口软件
手机APP-蓝牙调试器的设置方法
调试成功 :蓝牙软件和串口软件能够通讯
练一练–蓝牙控制小灯
连接如图
通过发送A或者B 控制单片机小灯反转
那么上面我们就完成了蓝牙的基本控制
然后我们就可以蓝牙反转灯的时候控制小车前行停止
//串口 if(g_USART1_FLAG1 == 1){g_USART1_FLAG1 = 0;//左电机慢速正转 AIN1=0;AIN2=1;TIM_SetCompare4(TIM1,1700); //设置//右边电机慢速执行BIN1 =1;BIN2 =0;TIM_SetCompare1(TIM1,1700); LED =! LED; }if(g_USART1_FLAG1 == 2) {g_USART1_FLAG1 = 0; //双电机停止BIN1 = 0;BIN2 = 0;AIN1 = 0;AIN2 =0;LED =! LED; }
上面是通过串口一(PA9 PA10)
蓝牙硬件是串口三(PB10 PB11)下面我们通过串口三实现
初始化使用串口3
//初始化串口3
void uart_init_3(u32 bound){//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能USART3RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //,GPIOB时钟//USART3_TX GPIOB.10GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB.10GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB.10//USART3_RX GPIOB.11初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB.11 //Usart3 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式USART_Init(USART3, &USART_InitStructure); //初始化串口3USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(USART3, ENABLE); //使能串口3 }
在main中定义标志位
int g_USART3_FLAG1 = 0; //串口3控制标志位
在usart.h中声明变量
extern int g_USART3_FLAG1 ;
在中断服务函数添加处理
//串口3 中断处理函数
void USART3_IRQHandler (void)
{u8 Res; if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {Res =USART_ReceiveData(USART3); //读取接收到的数据if(Res == 'A') g_USART3_FLAG1 = 1 ; //根据接受的数据 置为标志位if(Res == 'B')g_USART3_FLAG1 = 2 ;}
}
调用初始化函数
uart_init_3(115200); //初始化串口3
在main.c 编写逻辑
while(1){ //串口 if(g_USART3_FLAG1 == 1){ g_USART3_FLAG1 = 0;//左电机慢速正转 AIN1=0;AIN2=1;TIM_SetCompare4(TIM1,1700); //设置//右边电机慢速执行BIN1 =1;BIN2 =0;TIM_SetCompare1(TIM1,1700); LED =! LED; }if(g_USART3_FLAG1 == 2) {g_USART3_FLAG1 = 0; //双电机停止BIN1 = 0;BIN2 = 0;AIN1 = 0;AIN2 =0;LED =! LED; } }
把蓝牙安装顺序连接到STM32
跳线帽改至蓝牙
手机连接蓝牙 使用蓝牙调试器发送 A 或者 B
现象:发送A 小车直行、发送B小车停止。
练一练–蓝牙控制小车运动
USART中断服务函数
//串口3 中断处理函数
void USART3_IRQHandler (void)
{u8 Res; if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {Res =USART_ReceiveData(USART3); //读取接收到的数据if(Res == 'A') g_USART3_FLAG1 = 1 ; //根据接受的数据 置为标志位if(Res == 'B')g_USART3_FLAG1 = 2 ;if(Res == 'C') g_USART3_FLAG1 = 3 ; //根据接受的数据 置为标志位if(Res == 'D')g_USART3_FLAG1 = 4 ;if(Res == 'E')g_USART3_FLAG1 = 5; }}
main 中的逻辑
while(1){if(g_USART3_FLAG1 == 1) //前进{g_USART3_FLAG1=0;Forward();delay_ms(500);}if(g_USART3_FLAG1 == 2) //向右{g_USART3_FLAG1=0;Rightward();delay_ms(500);}if(g_USART3_FLAG1 ==3) //向左{g_USART3_FLAG1=0;Leftward();delay_ms(500);}if(g_USART3_FLAG1 ==4) //向后{g_USART3_FLAG1=0;Backward();delay_ms(500);}if(g_USART3_FLAG1 ==5) //停止{g_USART3_FLAG1=0;AIN1=0;AIN2=0;BIN1=0;BIN2=0;delay_ms(500);}}
手机中蓝牙调试助手的设计
练一练–把数据发送给电脑串口助手和手机APP
前面我们介绍了,如何通过电脑或者蓝牙APP,向单片机发送数据,下面我们介绍如何:单片机如何向电脑和蓝牙APP发送数据。
库函数提供了相关串口函数,但是每次只能发送一个字节
USART_SendData(USART1,'X');//通过库函数发送字节数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);//判断发送标志位,是否发送结束
在正点原子例程中完成了对printf的重映射,所以我们可以轻松的通过printf ()函数向串口1 发送不定长数据,这是正点原子的例程
struct __FILE
{ int handle; }; FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{ x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{ while((USART1->SR&0X40)==0);//循环发送,直到发送完毕 通过SR寄存器判断是否发送完成 USART1->DR = (u8) ch; //通过DR寄存器发送数据return ch;
}
那么我们如何实现任意串口都可以任性发送那?
这里我们使用vsprintf 格式化字符串来完成
需要包含的头文件
#include "stdarg.h"
void UsartPrintf(USART_TypeDef * USARTx,char * fmt ,...)
{unsigned char UsartPrintfBuf[256]; //定义一个字符串数组va_list ap;//初始化指向参数列表的指针unsigned char *pStr = UsartPrintfBuf; //指针指向数组首地址va_start(ap,fmt);//将第一个可变参数的地址付给ap,即ap 指向可变参数列表的开始vsprintf((char *)UsartPrintfBuf, fmt,ap);//将参数fmt、ap 指向的可变参数一起转化成格式化字符串,放string数组中,作用同sprintf(),只是参数类型不同 va_end(ap); //清除指针while(*pStr != 0) //判断是否发送完字符串{//while(USART_GetFlagStatus(USART3,USART_FLAG_TC == RESET));//判断发送标志位,是否发送结束USART_SendData(USARTx,*pStr++);//通过库函数发送字符串//pStr ++;while(USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);//判断发送标志位,是否发送结束}
}
参考资料:
在main 中调用函数
UsartPrintf(USART3,"Distance:%dMode:%d",TCRT5000_Dist(),Mode);
在手机APP显示数据
OLED显示
找资料
链接:
0.96寸(4管脚)资料下载链接:
https://pan.baidu.com/s/1J57Izsv-PKmbwVrA2ynDzg 提取码:vktz
测试例程-现象正常-更改引脚-现象正常-移植到自己的工程
拷贝移植文件
一般移植传感器的xxx.c 和xxx.h
复制相关文件到工程
KEIL中添加文件
添加头文件路径
对比程序移植
根据原理图更改初始化函数
SCL–PC14
SDA–PC15
修改OLED_Init() 函数
//初始化SSD1306
void OLED_Init(void)
{ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能A端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15|GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHzGPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC14,15GPIO_SetBits(GPIOC,GPIO_Pin_15|GPIO_Pin_14); delay_ms(800);OLED_WR_Byte(0xAE,OLED_CMD);//--display offOLED_WR_Byte(0x00,OLED_CMD);//---set low column addressOLED_WR_Byte(0x10,OLED_CMD);//---set high column addressOLED_WR_Byte(0x40,OLED_CMD);//--set start line address OLED_WR_Byte(0xB0,OLED_CMD);//--set page addressOLED_WR_Byte(0x81,OLED_CMD); // contract controlOLED_WR_Byte(0xFF,OLED_CMD);//--128 OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverseOLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 dutyOLED_WR_Byte(0xC8,OLED_CMD);//Com scan directionOLED_WR_Byte(0xD3,OLED_CMD);//-set display offsetOLED_WR_Byte(0x00,OLED_CMD);//OLED_WR_Byte(0xD5,OLED_CMD);//set osc divisionOLED_WR_Byte(0x80,OLED_CMD);//OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode offOLED_WR_Byte(0x05,OLED_CMD);//OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge PeriodOLED_WR_Byte(0xF1,OLED_CMD);//OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartionOLED_WR_Byte(0x12,OLED_CMD);//OLED_WR_Byte(0xDB,OLED_CMD);//set VcomhOLED_WR_Byte(0x30,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enableOLED_WR_Byte(0x14,OLED_CMD);//OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
}
修改oled.h中的宏
//-----------------OLED IIC端口定义---------------- #define OLED_SCLK_Clr() GPIO_ResetBits(GPIOC,GPIO_Pin_14)//SCL
#define OLED_SCLK_Set() GPIO_SetBits(GPIOC,GPIO_Pin_14)#define OLED_SDIN_Clr() GPIO_ResetBits(GPIOC,GPIO_Pin_15)//SDA
#define OLED_SDIN_Set() GPIO_SetBits(GPIOC,GPIO_Pin_15)#define OLED_CMD 0 //写命令#define OLED_DATA 1 //写数据
调用初始化函数
OLED_Init(); //初始化OLED OLED_Clear();
在main使用显示函数
显示字符串
OLED_ShowString(6,3,"ABCDSDKJF",16);//显示一个字符号串OLED_ShowString(0,6,"GFGFGF:",16); OLED_ShowString(63,6,"FGFGFG:",16);
显示数据的一种方法
u8 string[10] = {0}; //定义在前面
...sprintf((char *)string,"Pitch:%.2f",pitch);OLED_ShowString(6,3,string,16);
ADC测量电池电压
ADC是嘛
百度百科介绍:
我们知道万用表 电压表可以测量电池,或者电路电压。那么我们是否可以通过单片机获得电压,方便我们监控电池状态
如何测量我们的锂电池电压那?锂电池电压12V左右,单片机ADC最大测量电压3.3V,这里我们需要分压电路分压。
通过测量ADC点的电压就可以计算VBAT_IN的电压。
移植程序
拷贝文件
在adc.c的程序
#include "adc.h"#include "delay.h"//初始化ADC
//这里我们仅以规则通道为例
//我们默认将开启通道0~3
void Adc_Init(void)
{ ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE ); //使能ADC1通道时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M//PA1 作为模拟通道输入引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器 ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1ADC_ResetCalibration(ADC1); //使能复位校准 while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束ADC_StartCalibration(ADC1); //开启AD校准while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束// ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能}
//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)
{//设置指定ADC的规则组通道,一个序列,采样时间ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期 ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能 while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}u16 Get_Adc_Average(u8 ch,u8 times)
{u32 temp_val=0;u8 t;for(t=0;t<times;t++){temp_val+=Get_Adc(ch);delay_ms(5);}return temp_val/times;
}
初始化函数
Adc_Init(); //ADC初始化
在main中测量并显示
while(1){adcx=Get_Adc_Average(ADC_Channel_4,10);//LCD_ShowxNum(156,130,adcx,4,16,0);//显示ADC的值temp=(float)adcx*(3.3/4096);adcx=temp;sprintf((char *)string,"temp:%.2f",temp);OLED_ShowString(6,3,string,16); }
超声波测距
通过超声波的硬件介绍我们知道
MCU给Trig脚一个大于10us的高电平脉冲;然后读取Echo脚的高电平信号时间,通过公式:距离 = T*声速/2 就可以算出来距离。
软件方面:10us高电平脉冲通过GPIO输出实现,高电平信号时间我们通过定时器的输入捕获来计算的。
初始化脉冲引脚PA0
在led.c中的SR04初始化函数
void SR04_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA,GPIO_Pin_0);
}
led.h有关宏定义和声明
#define SR04 PAout(0) // PA0
void SR04_GPIO_Init(void);
初始化PA1输入捕获
查看数据手册
初始化定时器2 通道2 输入捕获相关功能
//定时器2通道2输入捕获配置TIM_ICInitTypeDef TIM2_ICInitStructure;void TIM2_Cap_Init(u16 arr,u16 psc)
{ GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1 清除之前设置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA1 输入 GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA1 下拉//初始化定时器5 TIM5 TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频器 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位//初始化TIM5输入捕获参数TIM2_ICInitStructure.TIM_Channel = TIM_Channel_2; // 选择输入端 IC2映射到TI2上TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI2上TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频 TIM2_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波TIM_ICInit(TIM2, &TIM2_ICInitStructure);//中断分组初始化NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);//允许更新中断 ,允许CC2IE捕获中断 TIM_Cmd(TIM2,ENABLE ); //使能定时器2}
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值//定时器2中断服务程序
void TIM2_IRQHandler(void)
{ if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获 { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){ if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了{if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了{TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次TIM5CH1_CAPTURE_VAL=0XFFFF;}else TIM5CH1_CAPTURE_STA++;} }if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)//捕获2发生捕获事件{ if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿 { TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次上升沿TIM5CH1_CAPTURE_VAL=TIM_GetCapture2(TIM2);TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising); //CC2P=0 设置为上升沿捕获}else //还未开始,第一次捕获上升沿{TIM5CH1_CAPTURE_STA=0; //清空TIM5CH1_CAPTURE_VAL=0;TIM_SetCounter(TIM2,0);TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Falling); //CC2P=1 设置为下降沿捕获} } }TIM_ClearITPendingBit(TIM2, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位}
在time 中声明初始化函数
void TIM2_Cap_Init(u16 arr,u16 psc);
计算输出距离
在main.c声明变量
extern u8 TIM5CH1_CAPTURE_STA; //输入捕获状态
extern u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
定义变量
int Distance =0;
int time=0;
调用初始化函数
SR04_GPIO_Init();
TIM2_Cap_Init(0XFFFF,72-1); //以1Mhz的频率计数
完成测距的函数
delay_ms(500);//加入延时HC_SR04 =0;delay_us(10);HC_SR04 = 1; if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿{time=TIM5CH1_CAPTURE_STA&0X3F;time*=65536;//溢出时间总和time+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间printf("\r\nHIGH:%d us\r\n",time);//打印总的高点平时间Distance = time*0.033/2;printf("cm:%d\r\n",Distance);TIM5CH1_CAPTURE_STA=0;//开启下一次捕获}
封装一下方便调用
int TCRT5000_Dist(void)
{HC_SR04 = 1;delay_us(13);HC_SR04=0;if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿{time=TIM5CH1_CAPTURE_STA&0X3F;time*=65536;//溢出时间总和time+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间printf("\r\nHIGH:%d us\r\n",time);//打印总的高点平时间Distance = time*0.033/2;printf("cm:%d\r\n",Distance);TIM5CH1_CAPTURE_STA=0;//开启下一次捕获}return Distance;}
使用串口助手查看结果
练一练–编写定距离跟随功能
功能:根据根据超声波测量距离跟随前方物体
while(1){HC_SR04 = 1;delay_us(13);HC_SR04=0;if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿{time=TIM5CH1_CAPTURE_STA&0X3F;time*=65536;//溢出时间总和time+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间printf("\r\nHIGH:%d us\r\n",time);//打印总的高点平时间Distance = time*0.033/2;printf("cm:%d\r\n",Distance);TIM5CH1_CAPTURE_STA=0;//开启下一次捕获}if(Distance>20){Forward();delay_ms(50);}if(Distance<15){Backward();delay_ms(50);}AIN1 =0;AIN2 = 0;BIN1 = 0;BIN2 =0;}
练一练–结合舵机完成避障功能
功能:通过舵机旋转不同角度,超声波测量左右是否存在障碍物,控制小车运动。
测试舵机的转角,不同占空比小车舵机的角度
TIM_SetCompare1(TIM3,80);
TIM_SetCompare1(TIM3,50);
TIM_SetCompare1(TIM3,110);
整体逻辑
if(Mode == 3){ //超声波避障TIM_SetCompare1(TIM3,80); //舵机向前 使超声波朝前方delay_ms(200);if(TCRT5000_Dist()>25)// 前方无障碍物{Forward();delay_ms(500);}if(TCRT5000_Dist()<25) //向前有障碍物{TIM_SetCompare1(TIM3,50); //舵机向右边转大约30度delay_ms(200);if(TCRT5000_Dist()>25)//右侧无障碍物判断{Rightward();delay_ms(700);}else { //右边有障碍物TIM_SetCompare1(TIM3,100); //舵机向左边转大约30度delay_ms(200);if(TCRT5000_Dist()>25)//左侧无障碍物{Leftward();delay_ms(700); }else{Backward();//后退delay_ms(700);Rightward(); //右转delay_ms(700);} }} } }
综合一下-缝合上面练一练的功能
功能:
- 小车具有红外对管循迹、蓝牙遥控、定距离跟随、避障运动模式
- 可以通过小车按键和APP进行切换小车的运动模式。
- APP与OLED显示小车所处模式和超声波测量值、电池电压。
实现切换功能必须
main中的循环
while(1){ sprintf((char *)string,"Distance:%d ",TCRT5000_Dist());// 显示距离信息 这里的 %d 需要几个空格OLED_ShowString(6,3,string,16); sprintf((char *)string,"Mode:%d",Mode);//显示小车模式OLED_ShowString(6,6,string,16); if(Mode == 1){//定距离跟随TIM_SetCompare1(TIM3,80); //超声波舵机向前if(TCRT5000_Dist()>25)// 距离太远{Forward();delay_ms(200);}if(TCRT5000_Dist() <20)//距离太近{Backward();delay_ms(200); }AIN1 =0;//车辆暂定 如果不加 小车就会一直往前或者一直往后AIN2 =0;BIN1 =0;BIN2 =0; }if(Mode == 2){//蓝牙控制小车TIM_SetCompare1(TIM3,80); //超声波舵机向前if(g_USART3_FLAG1 == 1) //前进{g_USART3_FLAG1=0;Forward();delay_ms(500);}if(g_USART3_FLAG1 == 2) //向右{g_USART3_FLAG1=0;Rightward();delay_ms(500);}if(g_USART3_FLAG1 ==3) //向左{g_USART3_FLAG1=0;Leftward();delay_ms(500);}if(g_USART3_FLAG1 ==4) //向后{g_USART3_FLAG1=0;Backward();delay_ms(500);}if(g_USART3_FLAG1 ==5) //停止{g_USART3_FLAG1=0;AIN1=0;AIN2=0;BIN1=0;BIN2=0;delay_ms(500);}} if(Mode == 3){ //超声波避障TIM_SetCompare1(TIM3,80); //舵机向前delay_ms(200);if(TCRT5000_Dist()>25)// 前方无障碍物{Forward();delay_ms(500);}if(TCRT5000_Dist()<25) //向前有障碍物{TIM_SetCompare1(TIM3,50); //舵机右转delay_ms(200);if(TCRT5000_Dist()>25)//右侧无障碍物判断{Rightward();delay_ms(700);}else {TIM_SetCompare1(TIM3,100); //舵机向左 delay_ms(200);if(TCRT5000_Dist()>25)//左侧无障碍物{Leftward();delay_ms(700); }else{Backward();delay_ms(700);//后退Rightward();delay_ms(700);} }} }if(Mode == 4){//红外循迹TIM_SetCompare1(TIM3,80); //超声波舵机向前if(HW_1 == 0&&HW_2 == 0&&HW_3 == 0&&HW_4 == 0)//应该前进 {Forward();delay_ms(20);}if(HW_1 == 0&&HW_2 == 1&&HW_3 == 0&&HW_4 == 0)//应该右边{Rightward();delay_ms(150); }if(HW_1 == 1&&HW_2 == 0&&HW_3 == 0&&HW_4 == 0)//应该右边{Rightward();delay_ms(270);}if(HW_1 == 1&&HW_2 == 1&&HW_3 == 0&&HW_4 == 0)//应该右边{Rightward();delay_ms(370);}if(HW_1 == 0&&HW_2 == 0&&HW_3 == 1&&HW_4 == 0)//应该左边{Leftward();delay_ms(150);}if(HW_1 == 0&&HW_2 == 0&&HW_3 == 0&&HW_4 == 1)//应该左边{ Leftward();delay_ms(270); }if(HW_1 == 0&&HW_2 == 0&&HW_3 == 1&&HW_4 == 1)//应该左边{Leftward();delay_ms(370); }} if(Mode ==0){delay_ms(1);AIN1=0;AIN2=0;BIN1=0;BIN2=0;}}
stm32项目_stm32f103c8t6项目_循迹避障小车完整制作过程_智能小车设计_STM32智能小车教程-循迹-避障-蓝牙遥控-跟随相关推荐
- 基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----上篇(第123点)
基于STM32F103C8T6的循迹避障小车完整制作过程 本文适合小白观看 由于本人的一个小项目,要做一个基于STM32的循迹避障小车,前后花了约1周的时间,这个过程中也参考了很多大神分享的资料,学到 ...
- 基于STC12C5616AD芯片智能循迹避障小车完整制作过程(详细教程)
前言:本篇文章适合小白阅读,其中有很基础的Keil 5的使用教程等.大多网友知道如何使用,因此大家可以看目录,对于自己而言比较基础的可以不用阅读,重点关注一些迷茫的部分. 智能循迹避障小车教程目录 智 ...
- 诺宝机器人制作过程_深圳诺宝机器人教案
<深圳诺宝机器人教案>由会员分享,可在线阅读,更多相关<深圳诺宝机器人教案(2页珍藏版)>请在人人文库网上搜索. 1.深圳诺宝机器人教案一.前学期教与学的情况分析二.对本学期教 ...
- 我的世界服务器被无限循环怎么办,我的世界怎么制作无限循环装置_我的世界无限循环装置制作方法_游戏堡...
在我的世界游戏里面,无限循环装置是个非常实用的装置,玩家可以通过这个实现刷怪,或者一些意想不到的功能,可能很多玩家不知道怎样实现,下面游戏堡小编为大家带来详细制作方法,希望对大家有所帮助. 无限循环装 ...
- python天气预报制作过程_天气变冷了,用Python给父母制作一个天气提醒小助手~...
背景 最近天气变冷了,和父母聊天中得知,他们查看每天的天气预报不太方便,我记得小时候,每晚 19:30 左右,一家人都要守在电视机旁边收听第二天的天气情况. 如今,对于我们年轻人来说,获取天气情况很方 ...
- STM32F103RCT6 实验代码之舵机+超声波避障小车(三)蓝牙遥控
STM32 蓝牙遥控 一.蓝牙简述 二.蓝牙遥控 一.蓝牙简述 我这次把小车的坑补上,这是小车的最后一个部分,蓝牙遥控.这个部分重点在于蓝牙,其实蓝牙可以相当于一个串口,用串口通信就可以进行手机与单片 ...
- 智能循迹避障小车C语言程序编写思路,基于单片机的智能小车红外避障循迹系统设计与制作...
余秀玲 余秀娟 摘 要:随着科技的高速发展,人们对生活质量的要求越来越高,无人驾驶汽车已经被广为研发和试用,由此智能小车的快速发展也是在情理之中.通过对基于单片机的智能小车的硬件及软件设计分析,实现红 ...
- java项目-第126期SSM的物流仓库管理系统-java毕业设计_计算机毕业设计
java项目-第126期SSM的物流仓库管理系统-java毕业设计_计算机毕业设计 [源码请到资源专栏下载] 大家好,今天分享的源码是<基于SpringBoot+Thymeleaf的物流仓库后台 ...
- 51单片机智能小车(舵机云台超声波避障+循迹+蓝牙+红外跟随)
**51单片机智能小车(舵机云台超声波避障+循迹+蓝牙+红外跟随+遥控+TFT液晶显示屏) 本人由于使用的液晶显示屏,程序大于8K,所以更换为STC12C5A60S2芯片,与51芯片兼容. 功能比较多 ...
最新文章
- 懂点 Nginx 反向代理与负载均衡,是面试加分项没有之一
- 在ORACLE中对存储过程加密
- CallContext和多线程
- head.s 剖析——Linux-0.11 剖析笔记(五)
- linux 线程id 进程id,在Linux上显示正在运行的进程的线程ID
- 开源android手写批注,浅析各阅读平台手写批注 智器阅读显身手
- windows 下安装Python
- OO第三次电梯作业优化
- 智能一代云平台(三十一):mybatis加入分页
- java static 变量,和方法从属于类
- 如何忽略SVN目录?
- 2017-7-8 OpenStack手工+oz自动制作CentOS 7.3镜像
- 浏览器地址栏传中文乱码
- iOS5编程--官方例子代码的研究--2.UICatalog-6
- http://wang-min-zhao-sina-com.iteye.com/blog/1467204
- 猪懂傻改之《powershell 代码规范》
- Autodesk的免费的CAD软件试用版下载地址
- python实现“幻影坦克”效果(点开图片是隐藏的另一张图)【详解】
- 如何在html上显示时间设置,js实现在网页上简单显示时间的方法
- 【独家】对话Trinity创始人李一灵:智能经济的基石
热门文章
- 中粮粮油化工厂人员定位解决方案,解决目前化化工厂普遍问题-新导智能
- 优麒麟 配置java_Ubuntu Kylin 优麒麟 安装部署Pentaho
- 社交效率管理,你get了么?——做一款基于日程的联系人管理工具
- deg怎么读_deg是什么意思_deg怎么读_deg翻译_用法_发音_词组_同反义词_degree(s) 度-新东方在线英语词典...
- 电脑技术员联盟 Ghost XP Sp3 装机版 V5.8下载 .
- Hash中的bucket什么意思?
- 【机器学习笔记】如何改进算法性能
- Unity ACT游戏相机逻辑
- shell命令使用sed从JSON中提取指定的值
- 学计算机的新手用什么笔记本好一点,笔记本什么配置比较好 有什么推荐呢