STM32——智能小车,STM32智能小车探索之旅

马肤

温馨提示:这篇文章已超过409天没有更新,请注意相关的内容是否还可用!

STM32是一款高性能的微控制器,广泛应用于智能小车领域。智能小车通过搭载STM32芯片,能够实现自主导航、智能避障、遥控操作等功能。STM32芯片具有高性能、低功耗、易于开发等特点,为智能小车的稳定性和性能提供了强有力的支持。STM32在智能小车领域的应用越来越广泛,成为智能小车的重要组成部分。

STM32——智能小车

硬件接线

STM32——智能小车,STM32智能小车探索之旅 第1张
(图片来源网络,侵删)

B-1A – PB0

B-1B – PB1

STM32——智能小车,STM32智能小车探索之旅 第2张
(图片来源网络,侵删)

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 12
char 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 位置为1
                    UART1_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();
                    else
                        stop();
                    memset(UART1_RX_Buffer, 0, UART1_REC_LEN);
                    UART1_RX_STA = 0;
                }
                else
                    // 否则认为接收错误,重新开始
                    UART1_RX_STA = 0;
            }
            else // 如果没有收到了 0x0d (回车)
            {
                //则先判断收到的这个字符是否是 0x0d (回车)
                if(buf == 0x0d)
                {
                    // 是的话则将 bit14 位置为1
                    UART1_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);
}
else
    stop();

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)  

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.小车测速

硬件接线

测速模块:

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-M3
    Flash 64K x 8bit
    SRAM 20K x 8bit
    GPIO 37个GPIO,分别为PA0-PA15、PBO-PB15、PC13-PC15、PDO-PD1
    ADC
    2个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 * CAN
        STM32F103C8T6单片机简介
        项目 介绍
        系统时
        钟
        内部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 .. PA15
        PB0, PB1, PB2 .. PB15
        PC0, PC1, PC2 .. PC15
        ...
        有一些特殊功能的引脚是不能用作IO的。
        内部框架图
        下图来源于官方参考手册,了解即可。
        推挽输出与开漏输出
        内部结构图
        推挽输出: 可以真正能真正的输出高电平和低电平
        开漏输出: 开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动
        如何点亮一颗LED灯
        标号一样的导线在物理上是连接在一起的。
        将PB8或PB9拉低,就可以实现将对应的LED灯点亮。
        编程实现点灯
        常用的GPIO HAL库函数:
        结构体 GPIO_InitTypeDef 定义:
        按键点亮LED灯(轮询法)
        输入(按键):
        KEY1:PA0
        KEY2:PA1
        输出(LED灯):
        LED1:PB8
        LED2:PB9
        void 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 1
uint8_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; /*!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=499
    LED1连接到哪个定时器的哪一路?
    学会看产品手册:
    开始实战!
    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++;
    else
        pwmVal--;
    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为5
    1.0ms------------45度; 5.0% 对应函数中CCRx为10
    1.5ms------------90度; 7.5% 对应函数中CCRx为15
    2.0ms-----------135度; 10.0% 对应函数中CCRx为20
    2.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 --- PB6
    Echo --- PB7
    LED1 --- 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) 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 位置为1
                    UART1_RX_STA |= 0x8000;
                else
                    // 否则认为接收错误,重新开始
                    UART1_RX_STA = 0;
            }
            else // 如果没有收到了 0x0d (回车)
            {
                //则先判断收到的这个字符是否是 0x0d (回车)
                if(buf == 0x0d)
                {
                    // 是的话则将 bit14 位置为1
                    UART1_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 -- RX1
    HC01_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] != '')
        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] != '')
        printf("指令发送错误:%s", UART1_RX_Buffer);
}
硬件清单
    ESP8266模块
    CH340
    杜邦线
    项目设计及实现
    项目设计
    串口1用于与ESP8266通讯,串口2连接PC,用于打印log,查看系统状态。
    项目实现
    注意:
    1. 工作中一般不直接在中断服务函数里处理数据,而是在收到数据后直接丢给队列,再处理数据;
    2. 在中断服务函数里尽量减少使用延时函数及打印函数。
    AP模式:
    #define SIZE 12
    char 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 位置为1
                    UART1_RX_STA |= 0x8000;
                    // 查看是否收到 WIFI GOT IP
                    if(!strcmp((uint8_t *)UART1_RX_Buffer, "WIFI GOT IP"))
                        AT_Connect_Net_Flag = 1;
                    // 查看是否收到 OK
                    if(!strcmp((uint8_t *)UART1_RX_Buffer, "OK"))
                        AT_OK_Flag = 1;
                    // 查看是否收到 FAIL
                    if(!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 建立TCPServer
char 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 位置为1
                    UART1_RX_STA |= 0x8000;
                    // 查看是否收到 WIFI GOT IP
                    if(!strcmp(UART1_RX_Buffer, "WIFI GOT IP"))
                        AT_Connect_Net_Flag = 1;
                    // 查看是否收到 OK
                    if(!strcmp(UART1_RX_Buffer, "OK"))
                        AT_OK_Flag = 1;
                    // 查看是否收到 FAIL
                    if(!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 位置为1
                    UART1_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 -- PA0
    UART1 -- 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 -- PB8
    LED2 -- PB9
    WWDG配置:
    编程实现:
    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)
        应用场合 防止程序跑飞,死循环,死机 检测程序时效,防止软件异常
        独立看门狗和窗口看门狗的异同点
        十、DMA
        DMA介绍
        什么是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优先于通道4
    DMA传输方式
    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 > 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_DMA
    HAL_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_DMAStop
    HAL_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.c
    main.h
    stm32f1xx_it.c
    uint8_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 100
extern 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 */
    十一、ADC
        ADC介绍
        ADC是什么?
        全称:Analog-to-Digital Converter,指模拟/数字转换器
        ADC的性能指标
        量程:能测量的电压范围
        分辨率:ADC能辨别的最小模拟量,通常以输出二进制数的位数表示,比如:8、10、12、16位等;位
        数越多,分辨率越高,一般来说分辨率越高,转化时间越长
        转化时间:从转换开始到获得稳定的数字量输出所需要的时间称为转换时间
        ADC特性
        12位精度下转换速度可高达1MHZ
        供电电压:V SSA :0V,V DDA :2.4V~3.6V
        ADC输入范围: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配置
        代码实现
        十二、IIC
        IIC介绍
        笔记参照:上官一号笔记第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 -- PB6
    SDA -- PB7
    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_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 off
    Oled_Write_Cmd(0x00);//---set low column address
    Oled_Write_Cmd(0x10);//---set high column address
    Oled_Write_Cmd(0x40);//--set start line address
    Oled_Write_Cmd(0xB0);//--set page address
    Oled_Write_Cmd(0x81); // contract control
    Oled_Write_Cmd(0xFF);//--128
    Oled_Write_Cmd(0xA1);//set segment remap
    Oled_Write_Cmd(0xA6);//--normal / reverse
    Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
    Oled_Write_Cmd(0x3F);//--1/32 duty
    Oled_Write_Cmd(0xC8);//Com scan direction
    Oled_Write_Cmd(0xD3);//-set display offset
    Oled_Write_Cmd(0x00);//
    Oled_Write_Cmd(0xD5);//set osc division
    Oled_Write_Cmd(0x80);//
    Oled_Write_Cmd(0xD8);//set area color mode off
    Oled_Write_Cmd(0x05);//
    Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
    Oled_Write_Cmd(0xF1);//
    Oled_Write_Cmd(0xDA);//set com pin configuartion
    Oled_Write_Cmd(0x12);//
    Oled_Write_Cmd(0xDB);//set Vcomh
    Oled_Write_Cmd(0x30);//
    Oled_Write_Cmd(0x8D);//set charge pump enable
    Oled_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 mode
    Oled_Write_Cmd (0x02); //page addressing mode
    for(i=0;i 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.c
    main.c
    4. 硬件PWM调速
    对应源代码:smartCar_project4
    硬件接线
    B-1A -- PA0
    B-1B -- PB1
    A-1A -- PA1
    A-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);
}
else
    stop();
// 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.c
    5. 左右轮各自调速
    对应源代码:smartCar_project5
    代码实现
    main.c
    6. 循迹小车
    对应源代码:smartCar_project6
    硬件接线
    B-1A -- PB0
    B-1B -- PB1
    A-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 -- 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)
            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 -- PB0
    B-1B -- PB1
    A-1A -- PB2
    A-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_project9
    9.1 封装摇头功能
    对应源代码:smartCar_project9_1
    硬件接线
    sg90 -- PB9
    cubeMX配置
{
    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.h
    main.c
    9.2 封装超声波传感器
    对应源代码:smartCar_project9_2
    硬件接线
    请注意,超声波模块的接线与垃圾桶项目有所不同!
    超声波模块:
    Trig       --    PB7
    Echo     -- 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);
#endif
initSG90();
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  35){
        //前进
        goForward();
    }else if(disMiddle 
                
                
                

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码