STM32——智能小车

硬件接线

B-1A – PB0

B-1B – PB1

A-1A – PB2

A-1B – PB10

其余接线参考51单片机小车项目。

1.让小车动起来

motor.c

#include "motor.h"void goForward(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}void goBack(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);}void goLeft(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}void goRight(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}void stop(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}

motor.h

#ifndef __MOTOR_H__#define __MOTOR_H__#include "main.h"void goForward(void);void goBack(void);void goLeft(void);void goRight(void);void stop(void);#endif

main.c

#include "motor.h"//main函数的while循环部分:while (1){/* USER CODE END WHILE */goForward();HAL_Delay(1000);goBack();HAL_Delay(1000);goLeft();HAL_Delay(1000);goRight();HAL_Delay(1000);stop();HAL_Delay(1000);/* USER CODE BEGIN 3 */}

2.串口控制小车

uart.c

#include "string.h"#include "stdio.h"#include "motor.h"//串口接收缓存(1字节)uint8_t buf=0;//定义最大接收字节数 200,可根据需求调整#define UART1_REC_LEN 200// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节uint8_t UART1_RX_Buffer[UART1_REC_LEN];// 接收状态// bit15, 接收完成标志// bit14, 接收到0x0d// bit13~0, 接收到的有效字节数目uint16_t UART1_RX_STA=0;#define SIZE 12char buffer[SIZE];// 接收完成回调函数,收到一个数据后,在这里处理void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){// 判断中断是由哪个串口触发的if(huart->Instance == USART1){// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)if((UART1_RX_STA & 0x8000) == 0){// 如果已经收到了 0x0d (回车),if(UART1_RX_STA & 0x4000){// 则接着判断是否收到 0x0a (换行)if(buf == 0x0a){// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1UART1_RX_STA |= 0x8000;// 灯控指令if(!strcmp(UART1_RX_Buffer, "M1"))goForward();else if(!strcmp(UART1_RX_Buffer, "M2"))goBack();else if(!strcmp(UART1_RX_Buffer, "M3"))goLeft();else if(!strcmp(UART1_RX_Buffer, "M4"))goRight();elsestop();memset(UART1_RX_Buffer, 0, UART1_REC_LEN);UART1_RX_STA = 0;}else// 否则认为接收错误,重新开始UART1_RX_STA = 0;}else // 如果没有收到了 0x0d (回车){//则先判断收到的这个字符是否是 0x0d (回车)if(buf == 0x0d){// 是的话则将 bit14 位置为1UART1_RX_STA |= 0x4000;}else{// 否则将接收到的数据保存在缓存数组里UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收if(UART1_RX_STA > UART1_REC_LEN - 1)UART1_RX_STA = 0;}}}// 重新开启中断HAL_UART_Receive_IT(&huart1, &buf, 1);}}int fputc(int ch, FILE *f){unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;}

main.c

#include "motor.h"extern uint8_t buf;//main函数HAL_UART_Receive_IT(&huart1, &buf, 1);

3.点动控制小车

uart.c

if (!strcmp(UART1_RX_Buffer, "M1")){goForward();HAL_Delay(10);}else if (!strcmp(UART1_RX_Buffer, "M2")){goBack();HAL_Delay(10);}else if (!strcmp(UART1_RX_Buffer, "M3")){goLeft();HAL_Delay(10);}else if (!strcmp(UART1_RX_Buffer, "M4")){goRight();HAL_Delay(10);}elsestop();

mian.c

// main函数里HAL_NVIC_SetPriority(SysTick_IRQn,0,0); //或者通过cubeMX配置while(1){stop();}

4.硬件PWM调速

硬件接线

B-1A – PA0

B-1B – PB1

A-1A – PA1

A-1B – PB10

其余接线参考上官一号小车项目。

main.c

// main函数里HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);while (1){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 8);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 8);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 15);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 15);HAL_Delay(1000);}

5.左右轮各自调速

main.c

// main函数里while (1){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);HAL_Delay(1000);}

6.循迹小车

硬件接线

B-1A – PB0

B-1B – PB1

A-1A – PB2

A-1B – PB10

循迹模块(左) – PB3

循迹模块(右) – PB4

#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)// main函数里while (1){if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)goForward();if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)goLeft();if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)goRight();if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)stop();}

7.循迹小车解决转弯平滑问题

硬件接线

B-1A – PA0

B-1B – PB1

A-1A – PA1

A-1B – PB10

循迹模块(左) – PB3

循迹模块(右) – PB4

#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)// main函数里while (1){if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,19);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,19);}if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);}if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);}if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,0);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,0);}}

8.跟随小车

硬件接线

B-1A – PB0

B-1B – PB1

A-1A – PB2

A-1B – PB10

跟随模块(左) – PB5

跟随模块(右) – PB6

#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)// main函数里while (1){if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)goForward();if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)goRight();if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)goLeft();if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)stop();}

9.摇头避障小车

硬件接线

sg90 – PB9

sg90.c

#include "sg90.h"#include "gpio.h"#include "tim.h"void initSG90(void){HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); //启动定时器4__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度}void sgMiddle(void){__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度}void sgRight(void){__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度}void sgLeft(void){__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 25); //将舵机置为180度}

SG90.h

#ifndef __SG90_H__#define __SG90_H__void initSG90(void);void sgMiddle(void);void sgRight(void);void sgLeft(void);#endif

main.c

initSG90();HAL_Delay(1000);while (1){sgLeft();HAL_Delay(1000);sgMiddle();HAL_Delay(1000);sgRight();HAL_Delay(1000);sgMiddle();HAL_Delay(1000);}

封装超声波传感器

超声波模块:

Trig – PB7

Echo – PB8

#include "sr04.h"#include "gpio.h"#include "tim.h"//使用TIM2来做us级延时函数void TIM2_Delay_us(uint16_t n_us){/* 使能定时器2计数 */__HAL_TIM_ENABLE(&htim2);__HAL_TIM_SetCounter(&htim2, 0);while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );/* 关闭定时器2计数 */__HAL_TIM_DISABLE(&htim2);}double get_distance(void){int cnt=0;//1. Trig ,给Trig端口至少10us的高电平HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);//拉高TIM2_Delay_us(20);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);//拉低//2. echo由低电平跳转到高电平,表示开始发送波//波发出去的那一下,开始启动定时器while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET);//等待输入电平拉高HAL_TIM_Base_Start(&htim2);__HAL_TIM_SetCounter(&htim2,0);//3. 由高电平跳转回低电平,表示波回来了while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET);//等待输入电平变低//波回来的那一下,我们开始停止定时器HAL_TIM_Base_Stop(&htim2);//4. 计算出中间经过多少时间cnt = __HAL_TIM_GetCounter(&htim2);//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)return (cnt*340/2*0.000001*100); //单位:cm}

sr04.h

#ifndef __SR04_H__#define __SR04_H__double get_distance(void);#endif

main.c

while (1){if(dir != MIDDLE){sgMiddle();dir = MIDDLE;HAL_Delay(300);}disMiddle = get_distance();if(disMiddle > 35){//前进}else{//停止//测左边距离sgLeft();HAL_Delay(300);disLeft = get_distance();sgMiddle();HAL_Delay(300);sgRight();dir = RIGHT;HAL_Delay(300);disRight = get_distance();}}

封装电机驱动

硬件接线

与 “让小车动起来” 完全一样

B-1A – PB0

B-1B – PB1

A-1A – PB2

A-1B – PB10

while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(dir != MIDDLE){sgMiddle();dir = MIDDLE;HAL_Delay(300);}disMiddle = get_distance();if(disMiddle > 35){//前进goForward();}else if(disMiddle < 10){goBack();}else{//停止stop();//测左边距离sgLeft();HAL_Delay(300);disLeft = get_distance();sgMiddle();HAL_Delay(300);sgRight();dir = RIGHT;HAL_Delay(300);disRight = get_distance();if(disLeft < disRight){goRight();HAL_Delay(150);stop();}if(disRight < disLeft){goLeft();HAL_Delay(150);stop();}}HAL_Delay(50);}

10.小车测速

硬件接线

测速模块:

VCC – 3.3V 不能接5V,否则遮挡一次会触发3次中断

OUT – PB14

unsigned int speedCnt;void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){if (GPIO_Pin == GPIO_PIN_14)if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)speedCnt++;}void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){printf("speed: %d\r\n", speedCnt);speedCnt = 0;}main函数里:HAL_TIM_Base_Start_IT(&htim2);

11.串口控制小车并使用Oled显示速度

硬件接线

SCL – PB6

SDA – PB7

封装Oled模块

12.Wi-Fi测速小车并本地Oled显示

硬件接线

把esp8266插进串口1

13.语音控制小车

硬件接线

循迹小车:

循迹模块(左) – PB3

循迹模块(右) – PB4

跟随小车:

跟随模块(左) – PA8

跟随模块(右) – PA9

避障小车:

sg90:PB9

Trig:PA10

Echo:PA11

OLED****模块:

SCL – PB6SDA – PB7

语音模块:

A25 – PA15 (跟随)

A26 – PA13 (避障)

A27 – PA14 (循迹)

上官二号-STM32F1单片机教程_2022版(良许)此教程以动手为主,解决大伙学了半天单片机不知道干什么的问题。上官二号(小朋友)涉及的内容和知识以小项目为基本单元(暂规划如下)小项目做啥就先讲啥,有目标有趣地来一起学习单片机喜欢不?课程要求:C语言熟练,如果C语言不好,可以学习上官老师录制的C语言课程。另外,最好提前学完C51 课程。课程特点:不会很正经,不会很学术,不会很理论,不喜勿入!一、开发环境的安装编程语言:C语言需要安装的软件有两个:Keil5 和 STM32CubeMX/* 01. 电动车报警器 ====》 IO控制入门 *//* 02. 感应开关盖垃圾桶 ====》 定时器,PWM开发,超声波 *//* 03. 基于wifi的智能控制插座 =====》 串口开发,ESP8266模块AT控制指令学习,中断学习*//* 04. 基于蓝牙HC-05的智能控制插座 =====》 串口开发,蓝牙穿透*//* 05. 基于4G的智能控制插座 =====》 串口开发,蓝牙穿透*//* 06. 温湿度检测系统 ======》 DS18B20单线协议,如何看时序图,IIC协议液晶屏显示,SPI协议液晶显示*//* 07. 语音控制开关灯 ======》 语音模块二次开发 *//* 08. 智能小车_远程控制/壁障/寻迹/数据采集等 ======》 综合性项目 */Keil5 的安装使用 Keil4 写 STM32 代码其实也是可以,但需要很复杂的配置,不建议新手操作。比较推荐 Keil5 编写 STM32 ,只需要一些简单的设置就可以上手,对新手友好。安装安装包(不需要太新,本课程以 MDK324 为例,最新的 MDK327 有问题)安装过程一路下一步即可(建议不要安装在 C 盘)安装路径一定不要有中文或空格!!(重要)Keil5 安装完之后,记得安装 F1 固件包破姐使用编程与编译过程与 Keil4 完全一样STM32F1 模板工程如何下载程序到上官二号烧录工具有很多种,比如:串口、J-Link、ST-Link、U-Link 等等,本教程使用 ST-Link。安装驱动官网下载(慢)https://www.st.com/en/development-tools/stsw-link009.html资料包接线配置STM32CubeMX 的安装作用通过界面的方式,快速生成工程文件。下载官网(慢)https://www.st.com/zh/development-tools/stm32cubemx.html#overview资料包安装一路下一步,建议不要安装在C盘配置更新固件包位置(比较大,默认在C盘,可以更改到其它盘)help --> update settings --> Firmware Repository使用STM32CubeMX生成工程文件1. 点击「ACCESS TO MCU SELECTOR」;2. 左上角搜索对应的芯片,并在右侧双击对应的芯片;3. 点击芯片对应的引脚,并进行配置;4. 配置工程名称及位置:1. 按下图配置 Coder Generator :6. 点击右上角 generate code :7. 点击 Open Project 即可调用 Keil5 打开自动生成的工程文件。二、初识STM32单片机什么是单片机?单片机(Single-Chip Microcomputer)是一种集成电路芯片,把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。STM系列单片机命名规则ST -- 意法半导体M -- Microelectronics 微电子32 -- 总线宽度项目 介绍内核 Cortex-M3Flash 64K x 8bitSRAM 20K x 8bitGPIO 37个GPIO,分别为PA0-PA15、PBO-PB15、PC13-PC15、PDO-PD1ADC2个12bit ADC合计12路通道,外部通道: PAO到PA7+PBO到PB1内部通道: 温度传感器通道ADC Channel 16和内部参考电压通道ADC Channel 17定时器/计数器4个16bit定时器/计数器,分别为TIM1、TIM2、TIM3、TIM4TM1带死区插入,常用于产生PWM控制电机看门狗定时器 2个看门狗定时器 (独立看门狗IWDG、窗口看门狗WWDG)滴答定时器1个24bit向下计数的滴答定时器systick工作电压、温度2V~3.6V、-40°C~85°C通信串口2 *IIC,2*SPI,3 *USART,1 * CANSTM32F103C8T6单片机简介项目 介绍系统时钟内部8MHz时钟HSI最高可倍频到64MHZ,外部8MHZ时钟HSE最高可倍频到72MHZ标准库与HAL库区别1. 寄存器寄存器众多,需要经常翻阅芯片手册,费时费力;更大灵活性,可以随心所欲达到自己的目的;深入理解单片机的运行原理,知其然更知其所以然。2. 标准库将寄存器底层操作都封装起来,提供一整套接口(API)供开发者调用每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的;配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能;大大降低单片机开发难度,但是在不同芯片间不方便移植。3. HAL库ST公司目前主力推的开发方式,新的芯片已经不再提供标准库;为了实现在不同芯片之间移植代码;为了兼容所有芯片,导致代码量庞大,执行效率低下。三、通用输入输出端口GPIO什么是GPIO?定义GPIO是通用输入输出端口的简称,简单来说就是STM32可控制的引脚STM32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。简单来说我们可以控制GPIO引脚的电平变化,达到我们的各种目的。命名规则组编号+引脚编号组编号:GPIOA, GPIOB, GPIOC, GPIOD .. GPIOG引脚编号:0,1,2,3,4...15组合起来:PA0, PA1, PA2 .. PA15PB0, PB1, PB2 .. PB15PC0, PC1, PC2 .. PC15...有一些特殊功能的引脚是不能用作IO的。内部框架图下图来源于官方参考手册,了解即可。推挽输出与开漏输出内部结构图推挽输出: 可以真正能真正的输出高电平和低电平开漏输出: 开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动如何点亮一颗LED灯标号一样的导线在物理上是连接在一起的。将PB8或PB9拉低,就可以实现将对应的LED灯点亮。编程实现点灯常用的GPIO HAL库函数:结构体 GPIO_InitTypeDef 定义:按键点亮LED灯(轮询法)输入(按键):KEY1:PA0KEY2:PA1输出(LED灯):LED1:PB8LED2:PB9void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);typedef struct{uint32_t Pin;uint32_t Mode;uint32_t Pull;uint32_t Speed;} GPIO_InitTypeDef;#define KEY_ON 0#define KEY_OFF 1uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin){if( HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == GPIO_PIN_RESET){/* 按键按下 */return KEY_ON;}else{/* 按键松开 */while(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET);return KEY_OFF;}}四、复位和时钟控制(RCC)复位系统复位当发生以下任一事件时,产生一个系统复位:1. NRST引脚上的低电平(外部复位)2. 窗口看门狗计数终止(WWDG复位)3. 独立看门狗计数终止(IWDG复位)4. 软件复位(SW复位)5. 低功耗管理复位电源复位当以下事件中之一发生时,产生电源复位:1. 上电/掉电复位(POR/PDR复位)2. 从待机模式中返回备份区复位备份区域拥有两个专门的复位,它们只影响备份区域。当以下事件中之一发生时,产生备份区域复位。1. 软件复位,备份区域复位可由设置备份域控制寄存器 (RCC_BDCR)(见6.3.9节)中的BDRST位产生。2. 在VDD和VBAT两者掉电的前提下,VDD或VBAT上电将引发备份区域复位。时钟控制什么是时钟?时钟打开,对应的设备才会工作。时钟来源三种不同的时钟源可被用来驱动系统时钟(SYSCLK)HSI振荡器时钟(高速内部时钟)HSE振荡器时钟(高速外部时钟)while (1){/* USER CODE END WHILE */if(Key_Scan(GPIOA,GPIO_PIN_0) == KEY_ON)HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);if(Key_Scan(GPIOA,GPIO_PIN_1) == KEY_ON)HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);/* USER CODE BEGIN 3 */}PLL时钟(锁相环倍频时钟)二级时钟源:40kHz低速内部RC(LSIRC)振荡器32.768kHz低速外部晶体(LSE晶体)如何使用CubeMX配置时钟五、中断和事件中断概述什么是中断?中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。什么是EXTI?外部中断/事件控制器(EXTI)管理了控制器的23个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。EXTI可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。产生中断线路目的是把输入信号输入到NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。EXTI初始化结构体:typedef struct{//中断/事件线uint32_t EXTI_Line; /*!< Specifies the EXTI lines to be enabled ordisabled.This parameter can be any combination valueof @ref EXTI_Lines *///EXTI 模式EXTIMode_TypeDef EXTI_Mode; /*!< Specifies the mode for the EXTI lines.This parameter can be a value of @refEXTIMode_TypeDef *///触发类型EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edgefor the EXTI lines.This parameter can be a value of @refEXTITrigger_TypeDef *///EXTI 控制FunctionalState EXTI_LineCmd; /*!< Specifies the new state of the selectedEXTI lines.This parameter can be set either to ENABLEor DISABLE */}EXTI_InitTypeDef;中断/事件线:#define EXTI_Line0 ((uint32_t)0x00001) /*!< External interrupt line 0 */#define EXTI_Line1 ((uint32_t)0x00002) /*!< External interrupt line 1 */#define EXTI_Line2 ((uint32_t)0x00004) /*!< External interrupt line 2 */#define EXTI_Line3 ((uint32_t)0x00008) /*!< External interrupt line 3 */#define EXTI_Line4 ((uint32_t)0x00010) /*!< External interrupt line 4 */#define EXTI_Line5 ((uint32_t)0x00020) /*!< External interrupt line 5 */#define EXTI_Line6 ((uint32_t)0x00040) /*!< External interrupt line 6 */#define EXTI_Line7 ((uint32_t)0x00080) /*!< External interrupt line 7 */#define EXTI_Line8 ((uint32_t)0x00100) /*!< External interrupt line 8 */#define EXTI_Line9 ((uint32_t)0x00200) /*!< External interrupt line 9 */#define EXTI_Line10 ((uint32_t)0x00400) /*!< External interrupt line 10 */#define EXTI_Line11 ((uint32_t)0x00800) /*!< External interrupt line 11 */#define EXTI_Line12 ((uint32_t)0x01000) /*!< External interrupt line 12 */#define EXTI_Line13 ((uint32_t)0x02000) /*!< External interrupt line 13 */#define EXTI_Line14 ((uint32_t)0x04000) /*!< External interrupt line 14 */#define EXTI_Line15 ((uint32_t)0x08000) /*!< External interrupt line 15 */#define EXTI_Line16 ((uint32_t)0x10000) /*!< External interrupt line 16Connected to the PVD Output */#define EXTI_Line17 ((uint32_t)0x20000) /*!< External interrupt line 17Connected to the RTC Alarm event */#define EXTI_Line18 ((uint32_t)0x40000) /*!< External interrupt line 18Connected to the USB OTG FS Wakeup from suspend event */#define EXTI_Line19 ((uint32_t)0x80000) /*!< External interrupt line 19Connected to the Ethernet Wakeup event */#define EXTI_Line20 ((uint32_t)0x00100000) /*!< External interrupt line 20Connected to the USB OTG HS (configured in FS) Wakeup event */#define EXTI_Line21 ((uint32_t)0x00200000) /*!< External interrupt line 21Connected to the RTC Tamper and Time Stamp events */#define EXTI_Line22 ((uint32_t)0x00400000) /*!Instance == TIM2)HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);}HAL_TIM_Base_Start_IT(&htim2);PWM模式1:在向上计数时,一旦 CNT  CCRx 时输出为无效电平,否则为有效电平。PWM模式2:在向上计数时,一旦 CNT  CCRx 时输出为有效电平,否则为无效电平。PWM周期与频率:PWM占空比:由TIMx_CCRx寄存器决定。PWM实验需求:使用PWM点亮LED1实现呼吸灯效果。LED灯为什么可以越来越亮,越来越暗?这是由不同的占空比决定的。如何计算周期/频率?假如频率为 2kHz ,则:PSC=71,ARR=499LED1连接到哪个定时器的哪一路?学会看产品手册:开始实战!1. 设置时钟2. 设置定时器记得把极性设置为Low,因为LED灯是低电平才亮。3. 配置工程4. 业务代码项目二:感应开关盖垃圾桶项目需求检测靠近时,垃圾桶自动开盖并伴随滴一声,2秒后关盖发生震动时,垃圾桶自动开盖并伴随滴一声,2秒后关盖按下按键时,垃圾桶自动开盖并伴随滴一声,2秒后关盖项目框图// 定义变量uint16_t pwmVal=0; //调整PWM占空比uint8_t dir=1; //设置改变方向。1:占空比越来越大;0:占空比越来越小// 使能 Timer4 第3通道 PWM 输出HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);// while循环实现呼吸灯效果while (1){HAL_Delay(1);if (dir)pwmVal++;elsepwmVal--;if (pwmVal > 500)dir = 0;if (pwmVal == 0)dir =1;//修改比较值,修改占空比__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);}硬件清单SG90舵机,超声波模块,震动传感器,蜂鸣器a. sg90舵机介绍及实战sg90舵机介绍PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右。确定周期/频率如果周期为20ms,则 PSC=7199,ARR=199角度控制0.5ms-------------0度; 2.5% 对应函数中CCRx为51.0ms------------45度; 5.0% 对应函数中CCRx为101.5ms------------90度; 7.5% 对应函数中CCRx为152.0ms-----------135度; 10.0% 对应函数中CCRx为202.5ms-----------180度; 12.5% 对应函数中CCRx为25编程实现需求:每隔1s,转动一个角度:0度 --> 45度 --> 90度 --> 135度 --> 180度 --> 0度接线:代码:b. 超声波传感器介绍及实战超声波传感器介绍怎么让它发送波Trig ,给Trig端口至少10us的高电平HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);while (1){HAL_Delay(1000);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 5);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 10);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 15);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 20);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, 25);}怎么知道它开始发了Echo信号,由低电平跳转到高电平,表示开始发送波怎么知道接收了返回波Echo,由高电平跳转回低电平,表示波回来了怎么算时间Echo引脚维持高电平的时间!波发出去的那一下,开始启动定时器波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间怎么算距离距离 = 速度 (340m/s)* 时间/2编程实战需求:使用超声波测距,当手离传感器距离小于5cm时,LED1点亮,否则保持不亮状态。接线:Trig --- PB6Echo --- PB7LED1 --- PB8定时器配置:使用 TIM2 ,只用作计数功能,不用作定时。将 PSC 配置为71,则计数 1 次代表 1us 。编写微秒级函数://使用TIM2来做us级延时函数void TIM2_Delay_us(uint16_t n_us){/* 使能定时器2计数 */__HAL_TIM_ENABLE(&htim2);__HAL_TIM_SetCounter(&htim2, 0);while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );/* 关闭定时器2计数 */__HAL_TIM_DISABLE(&htim2);}主函数://1. Trig ,给Trig端口至少10us的高电平//2. echo由低电平跳转到高电平,表示开始发送波//波发出去的那一下,开始启动定时器//3. 由高电平跳转回低电平,表示波回来了//波回来的那一下,我们开始停止定时器//4. 计算出中间经过多少时间//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)//每500毫秒测试一次距离int cnt;float distance;while (1){//1. Trig ,给Trig端口至少10us的高电平HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);//拉高TIM2_Delay_us(20);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);//拉低//2. echo由低电平跳转到高电平,表示开始发送波//波发出去的那一下,开始启动定时器while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET);//等待输入电平拉高HAL_TIM_Base_Start(&htim2);__HAL_TIM_SetCounter(&htim2,0);//3. 由高电平跳转回低电平,表示波回来了while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//等待输入电平变低//波回来的那一下,我们开始停止定时器HAL_TIM_Base_Stop(&htim2);//4. 计算出中间经过多少时间cnt = __HAL_TIM_GetCounter(&htim2);//5. 距离 = 速度 (340m/s)* 时间/2(计数1次表示1us)distance = cnt*340/2*0.000001*100; //单位:cmif(distance < 5)项目设计及实现项目设计超声波模块:Trig--PB6Echo -- PB7sg90舵机:PWM -- PB9按键:KEY1 -- PA0LED灯:LED1 -- PB8震动传感器:D0 -- PB5VCC -- 5V蜂鸣器:IO -- PB4VCC -- 3V3项目实现七、串口串口介绍参见以下视频:HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);elseHAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);//每500毫秒测试一次距离HAL_Delay(500);}常用函数介绍串口发送/接收函数:HAL_UART_Transmit(); 串口发送数据,使用超时管理机制HAL_UART_Receive(); 串口接收数据,使用超时管理机制HAL_UART_Transmit_IT(); 串口中断模式发送HAL_UART_Receive_IT(); 串口中断模式接收作用:以阻塞的方式发送指定字节的数据形参 1 :UART_HandleTypeDef 结构体类型指针变量形参 2:指向要发送的数据地址形参 3:要发送的数据大小,以字节为单位形参 4:设置的超时时间,以ms单位作用:以中断的方式接收指定字节的数据形参 1 是 UART_HandleTypeDef 结构体类型指针变量形参 2 是指向接收数据缓冲区形参 3 是要接收的数据大小,以字节为单位此函数执行完后将清除中断,需要再次调用以重新开启中断。串口中断回调函数:HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //发送中断回调函数HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //接收中断回调函数状态标记变量:USART_RX_STAHAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,uint8_t *pData, uint16_t Size, uint32_t Timeout)HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,uint8_t *pData, uint16_t Size)从0开始,串口中断接收到一个数据(一个字节)就自增1。当数据读取全部OK时候(回车和换行符号来的时候),那么 USART_RX_STA的最高位置1,表示串口数据接收全部完毕了,然后main函数里面可以处理数据了。串口接收中断流程串口实验(非中断)需求:接受串口工具发送的字符串,并将其发送回串口工具。硬件接线:TX -- A10RX -- A9一定要记得交叉接线!!串口配置:1. 选定串口2. 选择模式异步通讯3. 串口配置4. 使用MicroLIB库从魔术棒打开,这个勾勾一定要打上,否则 printf 无法重映射!编程实现:串口实验(中断)需求:通过中断的方法接受串口工具发送的字符串,并将其发送回串口工具。#include #include unsigned char ch[20] = {0};int fputc(int ch, FILE *f){unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;}main函数里:unsigned char ch[20] = {0};HAL_UART_Transmit(&huart1, "hello world\n", strlen("hello world\n"), 100);while(1){HAL_UART_Receive(&huart1, ch, 19, 100);//HAL_UART_Transmit(&huart1, ch, strlen(ch), 100);printf(ch);memset(ch, 0, strlen(ch));}硬件接线:同上串口配置:前4步同上5. 打开中断编程实现:#include //串口接收缓存(1字节)uint8_t buf=0;//定义最大接收字节数 200,可根据需求调整#define UART1_REC_LEN 200// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节uint8_t UART1_RX_Buffer[UART1_REC_LEN];// 接收状态// bit15, 接收完成标志// bit14, 接收到0x0d// bit13~0, 接收到的有效字节数目uint16_t UART1_RX_STA=0;// 接收完成回调函数,收到一个数据后,在这里处理void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){// 判断中断是由哪个串口触发的if(huart->Instance == USART1){// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)if((UART1_RX_STA & 0x8000) == 0){// 如果已经收到了 0x0d (回车),if(UART1_RX_STA & 0x4000){// 则接着判断是否收到 0x0a (换行)if(buf == 0x0a)// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1UART1_RX_STA |= 0x8000;else// 否则认为接收错误,重新开始UART1_RX_STA = 0;}else // 如果没有收到了 0x0d (回车){//则先判断收到的这个字符是否是 0x0d (回车)if(buf == 0x0d){// 是的话则将 bit14 位置为1UART1_RX_STA |= 0x4000;}else{// 否则将接收到的数据保存在缓存数组里UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收if(UART1_RX_STA > UART1_REC_LEN - 1)UART1_RX_STA = 0;}}}// 重新开启中断HAL_UART_Receive_IT(&huart1, &buf, 1);}}int fputc(int ch, FILE *f){unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;}main函数部分HAL_UART_Receive_IT(&huart1, &buf, 1);while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 *///判断判断串口是否接收完成if(UART1_RX_STA & 0x8000){printf("收到数据:");// 将收到的数据发送到串口HAL_UART_Transmit(&huart1, UART1_RX_Buffer, UART1_RX_STA & 0x3fff, 0xffff);// 等待发送完成项目三:蓝牙插座风扇灯项目需求通过蓝牙模块,实现手机控制蓝牙插座/风扇/灯。本质:1. 采用蓝牙的透传功能;2. 控制 IO 口的输出。项目框图硬件清单HC01蓝牙模块CH340杜邦线项目设计及实现while(huart1.gState != HAL_UART_STATE_READY);printf("\r\n");// 重新开始下一次接收UART1_RX_STA = 0;}printf("hello liangxu\r\n");HAL_Delay(1000);}项目设计HC01_TX -- RX1HC01_RX -- TX1项目实现1. 串口非中断法2. 串口中断法项目四:Wi-Fi插座风扇灯项目需求通过ESP8266模块,实现手机控制wifi插座/风扇/灯。项目框图HAL_UART_Receive(&huart1, ch, 19, 100);//HAL_UART_Transmit(&huart1, ch, strlen(ch), 100);//printf((char *)ch);printf("%s", ch);if (!strcmp((const char *)ch, "open")) {HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET)printf("LED1已打开\n");}else if(!strcmp((const char *)ch, "close")) {HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET)printf("LED1已关闭\n");} else {if(ch[0] != '\0')printf("指令发送错误:%s", ch);}printf("收到数据:");if (!strcmp((const char *)UART1_RX_Buffer, "open")) {HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET)printf("LED1已打开\n");}else if(!strcmp((const char *)UART1_RX_Buffer, "close")) {HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET)printf("LED1已关闭\n");} else {if(UART1_RX_Buffer[0] != '\0')printf("指令发送错误:%s", UART1_RX_Buffer);}硬件清单ESP8266模块CH340杜邦线项目设计及实现项目设计串口1用于与ESP8266通讯,串口2连接PC,用于打印log,查看系统状态。项目实现注意:1. 工作中一般不直接在中断服务函数里处理数据,而是在收到数据后直接丢给队列,再处理数据;2. 在中断服务函数里尽量减少使用延时函数及打印函数。AP模式:#define SIZE 12char buffer[SIZE];char LJWL[] = "AT+CWJAP=\"TP-LINK_3E30\",\"18650711783\"\r\n"; //入网指令char LJFWQ[] = "AT+CIPSTART=\"TCP\",\"192.168.0.130\",8880\r\n"; //连接服务器指令char TCMS[] = "AT+CIPMODE=1\r\n"; //透传指令char SJCS[] = "AT+CIPSEND\r\n"; //数据传输开始指令char CQMK[] = "AT+RST\r\n"; //重启模块指令char AT_OK_Flag = 0; //OK返回值的标志位char AT_Connect_Net_Flag = 0; //WIFI GOT IP返回值的标志位// 接收完成回调函数,收到一个数据后,在这里处理void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){// 判断中断是由哪个串口触发的if(huart->Instance == USART1){// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)if((UART1_RX_STA & 0x8000) == 0){// 如果已经收到了 0x0d (回车),if(UART1_RX_STA & 0x4000){// 则接着判断是否收到 0x0a (换行)if(buf == 0x0a){// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1UART1_RX_STA |= 0x8000;// 查看是否收到 WIFI GOT IPif(!strcmp((uint8_t *)UART1_RX_Buffer, "WIFI GOT IP"))AT_Connect_Net_Flag = 1;// 查看是否收到 OKif(!strcmp((uint8_t *)UART1_RX_Buffer, "OK"))AT_OK_Flag = 1;// 查看是否收到 FAILif(!strcmp((uint8_t *)UART1_RX_Buffer, "FAIL")){int i = 0;for(i = 0; i  UART1_REC_LEN - 1)UART1_RX_STA = 0;}}}// 重新开启中断HAL_UART_Receive_IT(&huart1, &buf, 1);}}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_USART1_UART_Init();MX_USART2_UART_Init();/* USER CODE BEGIN 2 */HAL_NVIC_SetPriority(SysTick_IRQn,0,0);// 开启接收中断HAL_UART_Receive_IT(&huart1, &buf, 1);HAL_UART_Transmit(&huart2, "let's go!!\r\n", strlen("let's go!!\r\n"), 100);//发送联网AT指令并等待成功printf(LJWL);//while(!AT_Connect_Net_Flag);while(!AT_OK_Flag) HAL_Delay(50);AT_OK_Flag = 0;//发送连服务器指令并等待成功printf(LJFWQ);while(!AT_OK_Flag) HAL_Delay(50);AT_OK_Flag = 0;//发送透传模式指令并等待成功printf(TCMS);while(!AT_OK_Flag) HAL_Delay(50);AT_OK_Flag = 0;//发送数据传输指令并等待成功printf(SJCS);while(!AT_OK_Flag) HAL_Delay(50);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */printf("liangxu shuai\r\n");HAL_UART_Transmit(&huart2, "hello liangxu\r\n", strlen("hello liangxu\r\n"),100);HAL_Delay(3000);}/* USER CODE END 3 */}STA模式:#include #include char buffer[SIZE];//1 工作在路由模式char LYMO[] = "AT+CWMODE=2\r\n";//2 使能多链接char DLJ[] = "AT+CIPMUX=1\r\n";//3 建立TCPServerchar JLFW[] = "AT+CIPSERVER=1\r\n"; // default port = 333//发送数据char FSSJ[] = "AT+CIPSEND=0,5\r\n";char AT_OK_Flag = 0; //OK返回值的标志位char AT_Connect_Net_Flag = 0; //WIFI GOT IP返回值的标志位char Client_Connect_Flag = 0;// 接收完成回调函数,收到一个数据后,在这里处理void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){// 判断中断是由哪个串口触发的if(huart->Instance == USART1){// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)if((UART1_RX_STA & 0x8000) == 0){// 如果已经收到了 0x0d (回车),if(UART1_RX_STA & 0x4000){// 则接着判断是否收到 0x0a (换行)if(buf == 0x0a){// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1UART1_RX_STA |= 0x8000;// 查看是否收到 WIFI GOT IPif(!strcmp(UART1_RX_Buffer, "WIFI GOT IP"))AT_Connect_Net_Flag = 1;// 查看是否收到 OKif(!strcmp(UART1_RX_Buffer, "OK"))AT_OK_Flag = 1;// 查看是否收到 FAILif(!strcmp(UART1_RX_Buffer, "0,CONNECT"))Client_Connect_Flag = 1;// 灯控指令if(!strcmp(UART1_RX_Buffer, "L-1"))HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);if(!strcmp(UART1_RX_Buffer, "L-0"))HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);memset(UART1_RX_Buffer, 0, UART1_REC_LEN);UART1_RX_STA = 0;}else// 否则认为接收错误,重新开始UART1_RX_STA = 0;}else // 如果没有收到了 0x0d (回车){//则先判断收到的这个字符是否是 0x0d (回车)if(buf == 0x0d){// 是的话则将 bit14 位置为1UART1_RX_STA |= 0x4000;}else{// 否则将接收到的数据保存在缓存数组里UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收if(UART1_RX_STA > UART1_REC_LEN - 1)UART1_RX_STA = 0;}}}// 重新开启中断HAL_UART_Receive_IT(&huart1, &buf, 1);}}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();项目五:4G遥控插座风扇灯/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_USART1_UART_Init();MX_USART2_UART_Init();/* USER CODE BEGIN 2 */HAL_NVIC_SetPriority(SysTick_IRQn,0,0);// 开启接收中断HAL_UART_Receive_IT(&huart1, &buf, 1);HAL_UART_Transmit(&huart2, "let's go\r\n", strlen("let's go\r\n"), 100);printf(LYMO);while(!AT_OK_Flag) HAL_Delay(50);AT_OK_Flag = 0;printf(DLJ);while(!AT_OK_Flag) HAL_Delay(50);AT_OK_Flag = 0;printf(JLFW);while(!Client_Connect_Flag) HAL_Delay(50);AT_OK_Flag = 0;if(Client_Connect_Flag){HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);}/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 *///4 发送数据printf(FSSJ);HAL_Delay(2000);printf("Hello");HAL_Delay(2000);}/* USER CODE END 3 */}项目需求通过4G模块,实现电脑控制插座/风扇/灯。项目框图注意:由于硬件的限制,上官二号无法直接带动 4G 模块,可以将 4G 模块的 VCC 和 GND 插到 CH340 的 5V 和GND 里。硬件清单4G模块CH340杜邦线项目设计及实现项目设计1. 服务器搭建参照C51课程;2. 代码修改其实可以直接复用上节课的代码,把不相关的代码删除即可项目实现八、独立看门狗 IWDG// 按视频删除不相关代码即可独立看门狗介绍什么是看门狗?在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”(watchdog) 。独立看门狗工作在主程序之外,能够完全独立工作,它的时钟是专用的低速时钟(LSI),由 VDD 电压供电, 在停止模式和待机模式下仍能工作。独立看门狗本质本质是一个 12 位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即IWDG_RESET 。如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。独立看门狗框图独立看门狗时钟独立看门狗的时钟由独立的RC振荡器LSI提供,即使主时钟发生故障它仍然有效,非常独立。启用IWDG后,LSI时钟会自动开启。LSI时钟频率并不精确,F1用40kHz。LSI经过一个8位的预分频器得到计数器时钟。分频系数算法:prer是IWDG_PR 的值。重装载寄存器重装载寄存器是一个12位的寄存器,用于存放重装载值,低12位有效,即最大值为4096,这个值的大小决定着独立看门狗的溢出时间。键寄存器键寄存器IWDG_KR可以说是独立看门狗的一个控制寄存器,主要有三种控制方式,往这个寄存器写入下面三个不同的值有不同的效果。溢出时间计算公式独立看门狗实验需求:开启独立看门狗,溢出时间为1秒,使用按键1进行喂狗。硬件接线:KEY1 -- PA0UART1 -- PA9/PA10溢出时间计算:PSC=64,RLR=625编程实现:九、窗口看门狗 WWDG窗口看门狗介绍什么是窗口看门狗?窗口看门狗用于监测单片机程序运行时效是否精准,主要检测软件异常,一般用于需要精准检测程序运行时间的场合。窗口看门狗的本质是一个能产生系统复位信号和提前唤醒中断的6位计数器。产生复位条件:当递减计数器值从 0x40 减到 0x3F 时复位(即T6位跳变到0)计数器的值大于 W[6:0] 值时喂狗会复位。产生中断条件:当递减计数器等于 0x40 时可产生提前唤醒中断 (EWI)。在窗口期内重装载计数器的值,防止复位,也就是所谓的喂狗。#include main函数:HAL_UART_Transmit(&huart1, "程序启动。。\n", strlen("程序启动。。\n"), 100);while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)HAL_IWDG_Refresh(&hiwdg);HAL_Delay(50);}窗口看门狗工作原理WWDG框图控制寄存器配置寄存器状态寄存器超时时间计算Tout是WWDG超时时间(没喂狗)Fwwdg是WWDG的时钟源频率(最大36M)4096是WWDG固定的预分频系数2^WDGTB是WWDG_CFR寄存器设置的预分频系数值T[5:0]是WWDG计数器低6位,最多63窗口看门狗实验需求:开启窗口看门狗,计数器值设置为 0X7F ,窗口值设置为 0X5F ,预分频系数为 8 。程序启动时点亮 LED1 ,300ms 后熄灭。在提前唤醒中断服务函数进行喂狗,同时翻转 LED2 状态。硬件接线:LED1 -- PB8LED2 -- PB9WWDG配置:编程实现:void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg){HAL_WWDG_Refresh(hwwdg);HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);}main函数MX_GPIO_Init();HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);HAL_Delay(300);MX_WWDG_Init();while (1){HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);HAL_Delay(40);对比点 独立看门狗 窗口看门狗时钟源 独立时钟,LSI (40KHz) ,不精确 PCLK1或PCLK3,精确复位条件 递减计数到0 窗口期外喂狗或减到0x3F中断 没有中断 计数值减到0x40可产生中断递减计数器位数 12位(最大计数范围:4096~0) 7位(最大计数范围:127~63)应用场合 防止程序跑飞,死循环,死机 检测程序时效,防止软件异常独立看门狗和窗口看门狗的异同点十、DMADMA介绍什么是DMA?令人头秃的描述:DMA(Direct Memory Access,直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用。简单描述:就是一个数据搬运工!!DMA的意义代替 CPU 搬运数据,为 CPU 减负。1. 数据搬运的工作比较耗时间;2. 数据搬运工作时效要求高(有数据来就要搬走);3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。}搬运什么数据?存储器、外设这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。三种搬运方式:存储器→存储器(例如:复制某特别大的数据buf)存储器→外设 (例如:将某数据buf写入串口TDR寄存器)外设→存储器 (例如:将串口RDR寄存器写入某数据buf)存储器→存储器存储器→外设外设→存储器DMA 控制器STM32F103有2个 DMA 控制器,DMA1有7个通道,DMA2有5个通道。一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。DMA1有7个通道:DMA2有5个通道DMA及通道的优先级优先级管理采用软件+硬件:软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级最高级>高级>中级>低级硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4DMA传输方式DMA_Mode_Normal(正常模式) 一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次DMA_Mode_Circular(循环传输模式) 当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式指针递增模式外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。实验一、内存到内存搬运实验要求使用DMA的方式将数组A的内容复制到数组B中,搬运完之后将数组B的内容打印到屏幕。CubeMX配置DMA 配置:重定向 printf 的话记得将下面这个勾打开:用到的库函数1. HAL_DMA_Start参数一:DMA_HandleTypeDef *hdma,DMA通道句柄参数二:uint32_t SrcAddress,源内存地址参数三:uint32_t DstAddress,目标内存地址参数四:uint32_t DataLength,传输数据长度。注意:需要乘以sizeof(uint32_t)返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress,uint32_t DstAddress, uint32_t DataLength)2. __HAL_DMA_GET_FLAG参数一:HANDLE,DMA通道句柄参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志返回值:FLAG的值(SET/RESET)代码实现1. 开启数据传输2. 等待数据传输完成3. 打印数组内容实验二、内存到外设搬运#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))#define BUF_SIZE 16// 源数组uint32_t srcBuf[BUF_SIZE] = {0x00000000,0x11111111,0x22222222,0x33333333,0x44444444,0x55555555,0x66666666,0x77777777,0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF};// 目标数组uint32_t desBuf[BUF_SIZE];int fputc(int ch, FILE *f){unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;}main函数里:// 开启数据传输HAL_DMA_Start(&hdma_memtomem_dma1_channel1,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);// 等待数据传输完成while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET);// 打印数组内容for (i = 0; i < BUF_SIZE; i++)printf("Buf[%d] = %X\r\n", i, desBuf[i]);实验要求使用DMA的方式将内存数据搬运到串口1发送寄存器,同时闪烁LED1。CubeMX配置DMA配置用到的库函数HAL_UART_Transmit_DMA参数一:UART_HandleTypeDef *huart,串口句柄参数二:uint8_t *pData,待发送数据首地址参数三:uint16_t Size,待发送数据长度返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size)代码实现1. 准备数据2. 将数据通过串口DMA发送实验三、外设到内存搬运实验要求使用DMA的方式将串口接收缓存寄存器的值搬运到内存中,同时闪烁LED1。CubeMX配置DMA配置:#define BUF_SIZE 1000// 待发送的数据unsigned char sendBuf[BUF_SIZE];main函数里// 准备数据for (i = 0; i > 28U)== UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) & UART_IT_MASK)): \(((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) &UART_IT_MASK)): \((__HANDLE__)->Instance- >CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))参数一:HANDLE,串口句柄参数二:INTERRUPT,需要使能的中断返回值:无2. HAL_UART_Receive_DMAHAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)参数一:UART_HandleTypeDef *huart,串口句柄参数二:uint8_t *pData,接收缓存首地址参数三:uint16_t Size,接收缓存长度返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)3. __HAL_UART_GET_FLAG#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR &(__FLAG__)) == (__FLAG__))参数一:HANDLE,串口句柄参数二:FLAG,需要查看的FLAG返回值:FLAG的值4. __HAL_UART_CLEAR_IDLEFLAG#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)参数一:HANDLE,串口句柄返回值:无5. HAL_UART_DMAStopHAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)参数一:UART_HandleTypeDef *huart,串口句柄返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)6. __HAL_DMA_GET_COUNTER#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)参数一:HANDLE,串口句柄返回值:未传输数据大小代码实现如何判断串口接收是否完成?如何知道串口收到数据的长度?使用串口空闲中断(IDLE)!串口空闲时,触发空闲中断;空闲中断标志位由硬件置1,软件清零利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:1. 使能IDLE空闲中断;2. 使能DMA接收中断;3. 收到串口接收中断,DMA不断传输数据到缓冲区;4. 一帧数据接收完毕,串口暂时空闲,触发串口空闲中断;5. 在中断服务函数中,清除中断标志位,关闭DMA传输(防止干扰);6. 计算刚才收到了多少个字节的数据。7. 处理缓冲区数据,开启DMA传输,开始下一帧接收。有三个文件需要修改:main.cmain.hstm32f1xx_it.cuint8_t rcvBuf[BUF_SIZE]; // 接收数据缓存数组uint8_t rcvLen = 0; // 接收一帧数据的长度__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断HAL_UART_Receive_DMA(&huart1,rcvBuf,100); // 使能DMA接收中断while (1){HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);HAL_Delay(300);}#define BUF_SIZE 100extern uint8_t rcvBuf[BUF_SIZE];extern uint8_t rcvLen;void USART1_IRQHandler(void){/* USER CODE BEGIN USART1_IRQn 0 *//* USER CODE END USART1_IRQn 0 */十一、ADCADC介绍ADC是什么?全称:Analog-to-Digital Converter,指模拟/数字转换器ADC的性能指标量程:能测量的电压范围分辨率:ADC能辨别的最小模拟量,通常以输出二进制数的位数表示,比如:8、10、12、16位等;位数越多,分辨率越高,一般来说分辨率越高,转化时间越长转化时间:从转换开始到获得稳定的数字量输出所需要的时间称为转换时间ADC特性12位精度下转换速度可高达1MHZ供电电压:V SSA :0V,V DDA :2.4V~3.6VADC输入范围:VREF- ≤ VIN ≤ VREF+采样时间可配置,采样时间越长, 转换结果相对越准确, 但是转换速度就越慢ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中HAL_UART_IRQHandler(&huart1);/* USER CODE BEGIN USART1_IRQn 1 */if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET)) // 判断IDLE标志位是否被置位{__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除标志位HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);rcvLen = BUF_SIZE - temp; //计算数据长度HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);//发送数据HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA}/* USER CODE END USART1_IRQn 1 */}ADC通道总共2个ADC(ADC1,ADC2),每个ADC有18个转换通道: 16个外部通道、 2个内部通道(温度传感器、内部参考电压)。外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路。规则组:正常排队的人;注入组:有特权的人(军人、孕妇)ADC转换顺序每个ADC只有一个数据寄存器,16个通道一起共用这个寄存器,所以需要指定规则转换通道的转换顺序。规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器来控制,控制关系如下:注入序列的转换顺序是从JSQx[ 4 : 0 ](x=4-JL[1:0])开始。只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。ADC触发方式1. 通过向控制寄存器ADC-CR2的ADON位写1来开启转换,写0停止转换。2. 也可以通过外部事件(如定时器)进行转换。ADC转化时间ADC是挂载在APB2总线(PCLK2)上的,经过分频器得到ADC时钟(ADCCLK),最高 14 MHz。12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。转换时间=采样时间+12.5个周期ADC转化模式扫描模式关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道打开扫描模式:扫描所有被ADC_SQRx或ADC_JSQR选中的所有通道单次转换/连续转换单次转换:只转换一次连续转换:转换一次之后,立马进行下一次转换实验:使用ADC读取烟雾传感器的值CubeMX配置代码实现十二、IICIIC介绍笔记参照:上官一号笔记第5章节;视频参照:上官一号92~103节函数封装用到的库函数:参数一:I2C_HandleTypeDef *hi2c,I2C设备句柄参数二:uint16_t DevAddress,目标器件的地址,七位地址必须左对齐参数三:uint16_t MemAddress,目标器件的目标寄存器地址参数四:uint16_t MemAddSize,目标器件内部寄存器地址数据长度while (1){HAL_ADC_Start(&hadc1); //启动ADC单次转换HAL_ADC_PollForConversion(&hadc1, 50); //等待ADC转换完成smoke_value = HAL_ADC_GetValue(&hadc1); //读取ADC转换数据printf("smoke_value = %f\r\n", 3.3/4096 * smoke_value);//printf("smoke_value = %d \r\n", smoke_value);HAL_Delay(500);}HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,uint16_t MemAddress,uint16_t MemAddSize,uint8_t *pData,uint16_t Size,uint32_t Timeout)参数五:uint8_t *pData,待写的数据首地址参数六:uint16_t Size,待写的数据长度参数七:uint32_t Timeout,超时时间返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)向OLED写命令的封装:向OLED写数据的封装:重做上官一号的IIC实验接线:SCL -- PB6SDA -- PB7void Oled_Write_Cmd(uint8_t dataCmd){HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,&dataCmd, 1, 0xff);}void Oled_Write_Data(uint8_t dataData){HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,&dataData, 1, 0xff);}void Oled_Write_Cmd(uint8_t dataCmd){HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,&dataCmd, 1, 0xff);}void Oled_Write_Data(uint8_t dataData){HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,&dataData, 1, 0xff);}void Oled_Init(void){Oled_Write_Cmd(0xAE);//--display offOled_Write_Cmd(0x00);//---set low column addressOled_Write_Cmd(0x10);//---set high column addressOled_Write_Cmd(0x40);//--set start line addressOled_Write_Cmd(0xB0);//--set page addressOled_Write_Cmd(0x81); // contract controlOled_Write_Cmd(0xFF);//--128Oled_Write_Cmd(0xA1);//set segment remapOled_Write_Cmd(0xA6);//--normal / reverseOled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)Oled_Write_Cmd(0x3F);//--1/32 dutyOled_Write_Cmd(0xC8);//Com scan directionOled_Write_Cmd(0xD3);//-set display offsetOled_Write_Cmd(0x00);//Oled_Write_Cmd(0xD5);//set osc divisionOled_Write_Cmd(0x80);//Oled_Write_Cmd(0xD8);//set area color mode offOled_Write_Cmd(0x05);//Oled_Write_Cmd(0xD9);//Set Pre-Charge PeriodOled_Write_Cmd(0xF1);//Oled_Write_Cmd(0xDA);//set com pin configuartionOled_Write_Cmd(0x12);//Oled_Write_Cmd(0xDB);//set VcomhOled_Write_Cmd(0x30);//Oled_Write_Cmd(0x8D);//set charge pump enableOled_Write_Cmd(0x14);//Oled_Write_Cmd(0xAF);//--turn on oled panel}void Oled_Screen_Clear(void){int i,n;Oled_Write_Cmd (0x20); //set memory addressing modeOled_Write_Cmd (0x02); //page addressing modefor(i=0;i<8;i++){Oled_Write_Cmd(0xb0+i); //éè??ò3μ??·£¨0~7£?Oled_Write_Cmd(0x00); //éè????ê??????aáDμíμ??·Oled_Write_Cmd(0x10); //éè????ê??????aáD??μ??·for(n=0;n<128;n++)Oled_Write_Data(0x00);}}unsigned char bmpImager[] = {/*-- 调入了一幅图像:D:\无标题.bmp --*//*-- 宽度x高度=128x64 --128x8x8*/0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x08,0x0C,0x04,0x06,0x06,0x0C,0x04,0x0C,0xFC,0x1C,0x74,0xFC,0xF8,0xF0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0x04,0x88,0xF8,0x08,0x08,0x0C,0x06,0x01,0x00,0x00,0x01,0x1F,0x7F,0xFF,0xDC,0xF8,0xE0,0xC0,0x40,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x10,0x18,0x08,0x0C,0x04,0x04,0x06,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x60,0xC0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x06,0x1C,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x88,0xE8,0x38,0x0E,0x09,0x08,0x08,0x88,0xE8,0x18,0x08,0x08,0x08,0x00,0x00,0xFF,0x89,0x89,0x89,0xFF,0x00,0xFF,0x89,0x89,0x89,0x89,0xFF,0x00,0x00,0x04,0x04,0x84,0x74,0x6F,0xA4,0x24,0x24,0x24,0x24,0xA4,0x64,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xF0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x10,0x08,0x09,0x09,0x06,0x06,0x06,0x05,0x08,0x08,0x10,0x10,0x00,0x00,0x0C,0x03,0x10,0x10,0x10,0x1F,0x18,0x07,0x00,0x00,0x10,0x10,0x1F,0x00,0x10,0x08,0x06,0x11,0x10,0x08,0x09,0x0A,0x06,0x06,0x0B,0x08,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x1E,0x03,0x00,0x00,0xC0,0x60,0x30,0x0C,0x04,0x06,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1E,0x60,0x78,0x0F,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,};void Oled_Show_Image(unsigned char *image){unsigned char i;unsigned int j;for(i=0;i<8;i++){Oled_Write_Cmd(0xB0 + i);//page0--page7//每个page从0列Oled_Write_Cmd(0x00);Oled_Write_Cmd(0x10);//0到127列,依次写入0,每写入数据,列地址自动偏移for(j = 128 * i; jODR = cmd;代码实现#define RS_GPIO_Port GPIOB#define RW_GPIO_Port GPIOB#define EN_GPIO_Port GPIOB#define RS_Pin GPIO_PIN_1#define RW_Pin GPIO_PIN_2#define EN_Pin GPIO_PIN_10#define RS_HIGH HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_SET)#define RS_LOW HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_RESET)#define RW_HIGH HAL_GPIO_WritePin(RW_GPIO_Port, RW_Pin, GPIO_PIN_SET)#define RW_LOW HAL_GPIO_WritePin(RW_GPIO_Port, RW_Pin, GPIO_PIN_RESET)#define EN_HIGH HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_SET)#define EN_LOW HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_RESET)void Write_Cmd_Func(uint8_t cmd){RS_LOW;RW_LOW;EN_LOW;GPIOA->ODR = cmd;HAL_Delay(5);EN_HIGH;HAL_Delay(5);EN_LOW;}void Write_Data_Func(uint8_t dataShow){RS_HIGH;RW_LOW;EN_LOW;GPIOA->ODR = dataShow;HAL_Delay(5);EN_HIGH;HAL_Delay(5);EN_LOW;}void LCD1602_INIT(void){//(1)延时 15msHAL_Delay(15);//(2)写指令 38H(不检测忙信号)Write_Cmd_Func(0x38);//(3)延时 5msHAL_Delay(5);//(4)以后每次写指令,读/写数据操作均需要检测忙信号//(5)写指令 38H:显示模式设置Write_Cmd_Func(0x38);b. DHT11介绍及实战//(6)写指令 08H:显示关闭Write_Cmd_Func(0x08);//(7)写指令 01H:显示清屏Write_Cmd_Func(0x01);//(8)写指令 06H:显示光标移动设置Write_Cmd_Func(0x06);//(9)写指令 0CH:显示开及光标设置}Write_Cmd_Func(0x0c);}void LCD1602_showLine(char row, char col, char *string){switch(row){case 1:Write_Cmd_Func(0x80+col);while(*string){Write_Data_Func(*string);string++;}break;case 2:Write_Cmd_Func(0x80+0x40+col);while(*string){Write_Data_Func(*string);string++;}break;}}main函数里://char position = 0x80 + 0x05;//char dataShow = 'C';LCD1602_INIT();//Write_Cmd_Func(position);//选择要显示的地址//Write_Data_Func(dataShow);//发送要显示的字符LCD1602_showLine(1,5,"NO.2");LCD1602_showLine(2,0,"LX handsome");硬件接线DAT -- PB7注意:PB7既作为输入,也作为输出,则不能直接在CubeMX里配置,需要自己写代码引脚封装代码实现#define DHT_HIGHT HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET)#define DHT_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET)#define DHT_VALUE HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)#define DHT_HIGHT HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET)#define DHT_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET)#define DHT_VALUE HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)uint8_t datas[5];void delay_us(uint16_t cnt){uint8_t i;while(cnt){for (i = 0; i < 10; i++){}cnt--;}}void DHT_GPIO_Init(uint32_t Mode){GPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_GPIOB_CLK_ENABLE();GPIO_InitStruct.Pin = GPIO_PIN_7;GPIO_InitStruct.Mode = Mode;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}void DHT11_Start(void){DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP);DHT_HIGHT;DHT_LOW;HAL_Delay(30);DHT_HIGHT;DHT_GPIO_Init(GPIO_MODE_INPUT);while(DHT_VALUE);while(!DHT_VALUE);while(DHT_VALUE);}void Read_Data_From_DHT(){int i;//轮int j;//每一轮读多少次char tmp;char flag;DHT11_Start();DHT_GPIO_Init(GPIO_MODE_INPUT);for(i= 0;i < 5;i++){for(j=0;j<8;j++){while(!DHT_VALUE);//等待卡g点delay_us(40);if(DHT_VALUE == 1){flag = 1;while(DHT_VALUE);}else{flag = 0;}tmp = tmp <= 25)HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);elseHAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);memset(message, 0, sizeof(message));sprintf(message, "Temp: %d.%d", datas[2], datas[3]);LCD1602_showLine(1,0,message);memset(message, 0, sizeof(message));sprintf(message, "Humi: %d.%d", datas[0], datas[1]);LCD1602_showLine(2,0,message);HAL_Delay(1000);}#include "motor.h"void goForward(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}void goBack(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);}void goLeft(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}void goRight(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}void stop(void){// 左轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);// 右轮HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);}motor.hmain.c2. 串口控制小车对应源代码:smartCar_project2代码实现usart.c#ifndef __MOTOR_H__#define __MOTOR_H__#include "main.h"void goForward(void);void goBack(void);void goLeft(void);void goRight(void);void stop(void);#endif#include "motor.h"//main函数的while循环部分:while (1){/* USER CODE END WHILE */goForward();HAL_Delay(1000);goBack();HAL_Delay(1000);goLeft();HAL_Delay(1000);goRight();HAL_Delay(1000);stop();HAL_Delay(1000);/* USER CODE BEGIN 3 */}#include "string.h"#include "stdio.h"#include "motor.h"//串口接收缓存(1字节)uint8_t buf=0;//定义最大接收字节数 200,可根据需求调整#define UART1_REC_LEN 200// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节uint8_t UART1_RX_Buffer[UART1_REC_LEN];// 接收状态// bit15, 接收完成标志// bit14, 接收到0x0d// bit13~0, 接收到的有效字节数目uint16_t UART1_RX_STA=0;#define SIZE 12char buffer[SIZE];// 接收完成回调函数,收到一个数据后,在这里处理void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){// 判断中断是由哪个串口触发的if(huart->Instance == USART1){// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)if((UART1_RX_STA & 0x8000) == 0){// 如果已经收到了 0x0d (回车),if(UART1_RX_STA & 0x4000){// 则接着判断是否收到 0x0a (换行)if(buf == 0x0a){// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1UART1_RX_STA |= 0x8000;// 灯控指令if(!strcmp(UART1_RX_Buffer, "M1"))goForward();else if(!strcmp(UART1_RX_Buffer, "M2"))goBack();else if(!strcmp(UART1_RX_Buffer, "M3"))goLeft();else if(!strcmp(UART1_RX_Buffer, "M4"))goRight();elsestop();memset(UART1_RX_Buffer, 0, UART1_REC_LEN);UART1_RX_STA = 0;}else// 否则认为接收错误,重新开始UART1_RX_STA = 0;main.c3. 点动控制小车对应源代码:smartCar_project3}else // 如果没有收到了 0x0d (回车){//则先判断收到的这个字符是否是 0x0d (回车)if(buf == 0x0d){// 是的话则将 bit14 位置为1UART1_RX_STA |= 0x4000;}else{// 否则将接收到的数据保存在缓存数组里UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收if(UART1_RX_STA > UART1_REC_LEN - 1)UART1_RX_STA = 0;}}}// 重新开启中断HAL_UART_Receive_IT(&huart1, &buf, 1);}}int fputc(int ch, FILE *f){unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;}#include "motor.h"extern uint8_t buf;//main函数HAL_UART_Receive_IT(&huart1, &buf, 1);代码实现usart.cmain.c4. 硬件PWM调速对应源代码:smartCar_project4硬件接线B-1A -- PA0B-1B -- PB1A-1A -- PA1A-1B -- PB10其余接线参考上官一号小车项目。if (!strcmp(UART1_RX_Buffer, "M1")){goForward();HAL_Delay(10);}else if (!strcmp(UART1_RX_Buffer, "M2")){goBack();HAL_Delay(10);}else if (!strcmp(UART1_RX_Buffer, "M3")){goLeft();HAL_Delay(10);}else if (!strcmp(UART1_RX_Buffer, "M4")){goRight();HAL_Delay(10);}elsestop();// main函数里HAL_NVIC_SetPriority(SysTick_IRQn,0,0); //或者通过cubeMX配置while(1){stop();}cubeMX配置TIM2配置如下图。这两节里配置有误,正确的应该是PSC=7199,ARR=199,大家注意修正!!设置 PSC=71 ,ARR=19,PWM 周期则为 20ms 。将控制车轮的4个 GPIO 口配置修改如下,否则小车动不起来。原因:L9110每个控制口需要一高一低才可以动起来,如果PWM有效电平为高电平,则另一个GPIO口则需要输出低电平才可以驱动轮子。代码实现main.c5. 左右轮各自调速对应源代码:smartCar_project5代码实现main.c6. 循迹小车对应源代码:smartCar_project6硬件接线B-1A -- PB0B-1B -- PB1A-1A -- PB2// main函数里HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);while (1){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 8);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 8);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 10);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 10);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, 15);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, 15);HAL_Delay(1000);}// main函数里while (1){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);HAL_Delay(1000);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);HAL_Delay(1000);}A-1B -- PB10循迹模块(左) -- PB3循迹模块(右) -- PB4其余接线参考上官一号小车项目。代码实现7. 循迹小车解决转弯平滑问题对应源代码:smartCar_project7硬件接线B-1A -- PA0B-1B -- PB1A-1A -- PA1A-1B -- PB10循迹模块(左) -- PB3循迹模块(右) -- PB4其余接线参考上官一号小车项目。代码实现#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)// main函数里while (1){if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)goForward();if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)goLeft();if (LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)goRight();if (LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)stop();}#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)// main函数里while (1)8. 跟随小车对应源代码:smartCar_project8硬件接线B-1A -- PB0B-1B -- PB1A-1A -- PB2A-1B -- PB10跟随模块(左) -- PB5跟随模块(右) -- PB6其余接线参考上官一号小车项目。代码实现{if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,19);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,19);}if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,15);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,8);}if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,8);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,15);}if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET){__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1,0);__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2,0);}}#define LeftWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)#define RightWheel_Value HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6)// main函数里while (1)9. 摇头避障小车对应源代码:smartCar_project99.1 封装摇头功能对应源代码:smartCar_project9_1硬件接线sg90 -- PB9cubeMX配置{if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_RESET)goForward();if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_RESET)goRight();if(LeftWheel_Value == GPIO_PIN_RESET && RightWheel_Value == GPIO_PIN_SET)goLeft();if(LeftWheel_Value == GPIO_PIN_SET && RightWheel_Value == GPIO_PIN_SET)stop();}代码实现sg90.c#include "sg90.h"#include "gpio.h"#include "tim.h"void initSG90(void){HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); //启动定时器4__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度}void sgMiddle(void){__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 17); //将舵机置为90度}void sgRight(void)sg90.hmain.c9.2 封装超声波传感器对应源代码:smartCar_project9_2硬件接线请注意,超声波模块的接线与垃圾桶项目有所不同!超声波模块:Trig--PB7Echo -- PB8{__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 5); //将舵机置为0度}void sgLeft(void){__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, 25); //将舵机置为180度}#ifndef __SG90_H__#define __SG90_H__void initSG90(void);void sgMiddle(void);void sgRight(void);void sgLeft(void);#endifinitSG90();HAL_Delay(1000);while (1){sgLeft();HAL_Delay(1000);sgMiddle();HAL_Delay(1000);sgRight();HAL_Delay(1000);sgMiddle();HAL_Delay(1000);}cubeMX配置代码实现sr04.c#include "sr04.h"#include "gpio.h"#include "tim.h"//使用TIM2来做us级延时函数void TIM2_Delay_us(uint16_t n_us){/* 使能定时器2计数 */__HAL_TIM_ENABLE(&htim2);__HAL_TIM_SetCounter(&htim2, 0);while(__HAL_TIM_GetCounter(&htim2)  35){//前进}else{//停止//测左边距离sgLeft();HAL_Delay(300);disLeft = get_distance();sgMiddle();HAL_Delay(300);sgRight();dir = RIGHT;HAL_Delay(300);disRight = get_distance();}}while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(dir != MIDDLE){sgMiddle();dir = MIDDLE;HAL_Delay(300);10. 小车测速对应源代码:smartCar_project10硬件接线测速模块:VCC -- 3.3V 不能接5V,否则遮挡一次会触发3次中断OUT -- PB14}disMiddle = get_distance();if(disMiddle > 35){//前进goForward();}else if(disMiddle < 10){goBack();}else{//停止stop();//测左边距离sgLeft();HAL_Delay(300);disLeft = get_distance();sgMiddle();HAL_Delay(300);sgRight();dir = RIGHT;HAL_Delay(300);disRight = get_distance();if(disLeft < disRight){goRight();HAL_Delay(150);stop();}if(disRight  35){//前进goForward();}else if(disMiddle < 10){goBack();}else{//停止stop();//测左边距离sgLeft();HAL_Delay(300);disLeft = get_distance();sgMiddle();HAL_Delay(300);sgRight();dir = RIGHT;HAL_Delay(300);disRight = get_distance();if(disLeft < disRight){goRight();HAL_Delay(150);stop();}if(disRight < disLeft){goLeft();HAL_Delay(150);stop();}}HAL_Delay(50);}int main(void){int mark = 0;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_TIM4_Init();MX_TIM2_Init();MX_I2C1_Init();/* USER CODE BEGIN 2 */initSG90();HAL_Delay(1000);dir = MIDDLE;Oled_Init();Oled_Screen_Clear();Oled_Show_Str(2,2,"-----Ready----");/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 *///满足循迹模式的条件if(XJ_VALUE == GPIO_PIN_RESET && GS_VALUE == GPIO_PIN_SET && BZ_VALUE == GPIO_PIN_SET){if(mark != XJ){Oled_Screen_Clear();Oled_Show_Str(2,2,"-----XunJi----");}mark = XJ;xunjiMode();}//满足跟随模式的条件if(XJ_VALUE == GPIO_PIN_SET && GS_VALUE == GPIO_PIN_RESET && BZ_VALUE == GPIO_PIN_SET){if(mark != GS){Oled_Screen_Clear();Oled_Show_Str(2,2,"-----GenSui----");}mark = GS;gensuiMode();}//满足避障模式的条件if(XJ_VALUE == GPIO_PIN_SET && GS_VALUE == GPIO_PIN_SET && BZ_VALUE == GPIO_PIN_RESET){if(mark != BZ){Oled_Screen_Clear();Oled_Show_Str(2,2,"-----BiZhang----");}mark = BZ;bizhangMode();}HAL_Delay(50);}/* USER CODE END 3 */}