STM32——智能小车
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 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 位置为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_PinStatePinState);
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; /*!< Specifies the EXTI lines to be enabled or
disabled.
This parameter can be any combination value
of @ref EXTI_Lines *///EXTI 模式EXTIMode_TypeDef EXTI_Mode; /*!< Specifies the mode for the EXTI lines.
This parameter can be a value of @ref
EXTIMode_TypeDef *///触发类型EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge
for the EXTI lines.
This parameter can be a value of @ref
EXTITrigger_TypeDef *///EXTI 控制FunctionalState EXTI_LineCmd; /*!< Specifies the new state of the selected
EXTI lines.
This parameter can be set either to ENABLE
or 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 16
Connected to the PVD Output */#define EXTI_Line17 ((uint32_t)0x20000) /*!< External interrupt line 17
Connected to the RTC Alarm event */#define EXTI_Line18 ((uint32_t)0x40000) /*!< External interrupt line 18
Connected to the USB OTG FS Wakeup from suspend event */#define EXTI_Line19 ((uint32_t)0x80000) /*!< External interrupt line 19
Connected to the Ethernet Wakeup event */#define EXTI_Line20 ((uint32_t)0x00100000) /*!< External interrupt line 20
Connected to the USB OTG HS (configured in FS) Wakeup event */#define EXTI_Line21 ((uint32_t)0x00200000) /*!< External interrupt line 21
Connected to the RTC Tamper and Time Stamp events */#define EXTI_Line22 ((uint32_t)0x00400000) /*!< External interrupt line 22
Connected to the RTC Wakeup event */EXTI模式:typedef enum
{EXTI_Mode_Interrupt = 0x00, //产生中断EXTI_Mode_Event = 0x04 //产生事件
}EXTIMode_TypeDef;
触发类型:typedef enum
{EXTI_Trigger_Rising = 0x08, //上升沿EXTI_Trigger_Falling = 0x0C, //下降沿EXTI_Trigger_Rising_Falling = 0x10 //上升沿和下降沿都触发
}EXTITrigger_TypeDef;
EXTI控制:使能 EXTI ,一般都是使能, ENABLE什么是优先级?抢占优先级和响应优先级的区别:高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行什么是优先级分组?Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此STM32把指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:第0组:所有4位用于指定响应优先级第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级第4组:所有4位用于指定抢占式优先级什么是NVIC?STM32通过中断控制器NVIC(Nested Vectored Interrupt Controller)进行中断的管理 。NVIC是属于Cortex内核的器件,不可屏蔽中断(NMI)和外部中断都由它来处理,但是SYSTICK不是由NVIC控制的。什么是中断向量表?每个中断源都有对应的处理程序,这个处理程序称为中断服务程序,其入口地址称为中断向量。所有中断的中断服务程序入口地址构成一个表,称为中断向量表;也有的机器把中断服务程序入口的跳转指令构成一张表,称为中断向量跳转表。按键点亮LED灯(中断法)1. 配置时钟2. 配置GPIO口3. 使能中断4. 配置工程项目一:电动车报警器项目需求点击遥控器 A 按键,系统进入警戒模式,一旦检测到震动(小偷偷车),则喇叭发出声响报警,吓退小偷。点击遥控器 B 按键,系统退出警戒模式,再怎么摇晃系统都不会报警,否则系统一直发出尖叫,让车主尴尬。项目框图typedef struct{uint8_t NVIC_IRQChannel;uint8_t NVIC_IRQChannelPreemptionPriority; //抢断优先级uint8_t NVIC_IRQChannelSubPriority; //响应优先级FunctionalState NVIC_IRQChannelCmd;} NVIC_InitTypeDef;
硬件清单振动传感器继电器高功率喇叭433M无线接收发射模块杜邦线a. 振动传感器介绍及实战振动传感器介绍单片机供电VCC GND接单片机产品不震动,输出高电平,模块上的DO口产品震动,输出低电平,绿色指示灯亮AO口不用编程实现需求:当振动传感器接收到振动信号时,使用中断方式点亮LED1。如果直接在中断服务函数里调用 HAL_Delay 函数,则会造成系统卡死。原因:程序初始化时默认把滴答定时器的中断优先级设为最低,其它中断源很容易打断它导致卡死。解决:在 main 函数里使用以下函数提高滴答定时器的中断优先级(提升至0)://重写中断服务函数,如果检测到EXTI中断请求,则进入此函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{//一根中断线上接有多个中断源,判断中断请求是否来自PA4if(GPIO_Pin == GPIO_PIN_4){//如果检测到PA4被拉低if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET){//则点亮LED1HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);//延时1秒HAL_Delay(1000);//关闭LED1HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);}else{//未检测到PA4被拉低,则关闭LED1HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);}}
}
HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
并且将 EXTI4 的中断优先级设置比滴答定时器的中断优先级高,比如 2 。b. 继电器介绍及实战继电器工作原理单片机供电VCC GND接单片机,VCC需要接3.3V,5V不行!最大负载电路交流250V/10A,直流30V/10A引脚 IN 接收到低电平时,开关闭合。编程实现c. 433M无线发射接收模块介绍及实战433M无线发射接收模块介绍单片机供电VCC GND接单片机接收到信号,接收模块对应针脚输出高电平有D0 D1 D2 D3,对应遥控器的ABCD编程实现需求:按下遥控器A按键,LED1亮1秒;按下遥控器B按键,LED2亮1秒。D0 -- PA5D1 -- PA6//重写中断服务函数,如果检测到EXTI中断请求,则进入此函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{switch(GPIO_Pin){// 如果检测到PA5被拉高(按键A被按下)项目设计及实现项目设计//如果检测到PA4被拉低(小偷偷车),并且警报模式打开//则将PB7拉低,继电器通电,喇叭一直响// 如果检测到PA5被拉高(按键A按下),设定为开启警报模式// 则将PB7拉低(喇叭响),2秒后恢复电平(喇叭不响),表示进入警报模式// 同时将标志位设置为ON// 如果检测到PA6被拉高(按键B按下),设定为关闭警报模式// 则将PB7拉低(喇叭响),1秒后恢复电平(喇叭不响),表示关闭警报模式// 同时将标志位设置为OFFcase GPIO_PIN_5:if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_SET){//则点亮LED1HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);HAL_Delay(1000);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);}else{//如果未检测到PA5,则关闭LED1HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);}break;// 如果检测到PA6被拉高(按键B按下)case GPIO_PIN_6:if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET){//则点亮LED2HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);HAL_Delay(1000);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);}else{//如果未检测到PA4,则关闭LED1HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);}break;}
}
编程实现#define J_ON 1#define J_OFF 0//重写中断服务函数,如果检测到EXTI中断请求,则进入此函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{static int mark = J_OFF;switch(GPIO_Pin){case GPIO_PIN_4://如果检测到PA4被拉低(小偷偷车),并且警报模式打开if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET && mark ==J_ON){//则将PB7拉低,继电器通电,喇叭一直响HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);}break;// 如果检测到PA5被拉高(按键A按下),设定为开启警报模式case GPIO_PIN_5:if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_SET){六、定时器Timer定时器介绍软件定时缺点:不精确、占用CPU资源定时器工作原理:使用精准的时基,通过硬件的方式,实现定时功能。定时器核心就是计数器。// 则将PB7拉低(喇叭响),2秒后恢复电平(喇叭不响),表示进入警报模式HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);HAL_Delay(2000);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);// 同时将标志位设置为ONmark = J_ON;}break;// 如果检测到PA6被拉高(按键B按下),设定为关闭警报模式case GPIO_PIN_6:if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET){// 则将PB7拉低(喇叭响),1秒后恢复电平(喇叭不响),表示关闭警报模式HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);HAL_Delay(1000);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);// 同时将标志位设置为OFFmark = J_OFF;}break;}
}
void Delay500ms() //@11.0592MHz
{unsigned char i, j, k;_nop_();i = 4;j = 129;k = 119;do{do{while (--k);} while (--j);} while (--i);
}
定时器分类:基本定时器(TIM6~TIM7)通用定时器(TIM2~TIM5)高级定时器(TIM1和TIM8)STM32F103C8T6定时器资源:通用定时器介绍:1) 16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。2) 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:A.输入捕获B.输出比较C.PWM 生成(边缘或中间对齐模式)D.单脉冲模式输出4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。5)如下事件发生时产生中断/DMA:A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)C.输入捕获D.输出比较E.支持针对定位的增量(正交)编码器和霍尔传感器电路F.触发输入作为外部时钟或者按周期的电流管理定时器计数模式:定时器时钟源:定时器溢出时间计算公式:例如,要定时500ms,则:PSC=7199,ARR=4999,Tclk=72M定时器中断实验需求:使用定时器中断方法,每500ms翻转一次LED1灯状态。1. RCC配置2. LED1灯配置3. 时钟数配置4. TIM2配置5. 工程配置6. 重写更新中断回调函数7. 启动定时器在main.c中,在定时器初始化命令之后加入以下代码:PWM介绍STM32F103C8T6 PWM资源:高级定时器(TIM1):7路通用定时器(TIM2~TIM4):各4路PWM输出模式:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2)HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
}
HAL_TIM_Base_Start_IT(&htim2);
PWM模式1:在向上计数时,一旦 CNT < CCRx 时输出为有效电平,否则为无效电平; 在向下计数时,一旦 CNT > CCRx 时输出为无效电平,否则为有效电平。PWM模式2:在向上计数时,一旦 CNT < CCRx 时输出为无效电平,否则为有效电平; 在向下计数时,一旦 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 <stdio.h>#include <string.h>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 <stdio.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;
// 接收完成回调函数,收到一个数据后,在这里处理
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 < 5; i++){HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);HAL_Delay(1000);}HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);printf(CQMK);}// 灯控指令if(!strcmp((uint8_t *)UART1_RX_Buffer, "L-1"))HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);if(!strcmp((uint8_t *)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();/* 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 <stdio.h>#include <string.h>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 位置为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 <string.h>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 < BUF_SIZE; i++)sendBuf[i] = 'A';
// 将数据通过串口DMA发送
HAL_UART_Transmit_DMA(&huart1, sendBuf, BUF_SIZE);
while (1)
{HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);HAL_Delay(100);
}
串口中断配置用到的库函数1. __HAL_UART_ENABLE#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((((__INTERRUPT__) >> 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 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 */十一、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; j<(128 * (i+1));j++){Oled_Write_Data(image[j]);}}
}
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_I2C1_Init();/* USER CODE BEGIN 2 *///1. OLED初始化十三、SPISPI 介绍SPI 是什么?SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 AT91RM9200 。以上介绍来自360百科SPI 物理架构Oled_Init();//2. 选择一个位置//2.1 确认页寻址模式Oled_Write_Cmd(0x20);Oled_Write_Cmd(0x02);Oled_Screen_Clear();Oled_Show_Image(bmpImager);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}
SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS、SCK、MOSI、MISO。它们的作用介绍如下 :(1) MISO – Master Input Slave Output,主设备数据输入,从设备数据输出(2) MOSI – Master Output Slave Input,主设备数据输出,从设备数据输入(3) SCK – Serial Clock,时钟信号,由主设备产生(4) CS – Chip Select,片选信号,由主设备控制SPI 工作原理SPI 工作模式时钟极性(CPOL):没有数据传输时时钟线的空闲状态电平0:SCK在空闲状态保持低电平1:SCK在空闲状态保持高电平时钟相位(CPHA):时钟线在第几个时钟边沿采样数据0:SCK的第一(奇数)边沿进行数据位采样,数据在第一个时钟边沿被锁存1:SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存模式 0 和模式 3 最常用。模式 0 时序图:模式 3 时序图:W25Q128 介绍什么是 W25Q128 ?W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128 Mbit,相当于 16M 字节。Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除。指令(HEX) 名称 作用0X06 写使能 写入数据/擦除之前,必须先发送该指令0X05 读SR1 判定FLASH是否处于空闲状态,擦除用0X03 读数据 用于读取NOR FLASH数据0X02 页写 用于写入NOR FLASH数据,最多写256字节0X20 扇区擦除 扇区擦除指令,最小擦除单位(4096字节)W25Q128 存储架构一般按扇区(4k)进行擦除。可以按 章 -- 节 -- 页 -- 字 进行理解。W25Q128 常用指令W25Q128 全部指令非常多,但常用的如下几个指令:写使能 (06H)执行页写,扇区擦除,块擦除,片擦除,写状态寄存器等指令前,需要写使能。拉低CS片选 → 发送06H → 拉高CS片选读状态寄存器(05H)拉低CS片选 → 发送05H→ 返回SR1的值 → 拉高CS片选读时序(03H)拉低CS片选 → 发送03H→ 发送24位地址 → 读取数据(1~n) → 拉高CS片选页写时序 (02H)页写命令最多可以向FLASH传输256个字节的数据。拉低CS片选 → 发送02H→ 发送24位地址 → 发送数据(1~n) → 拉高CS片选扇区擦除时序(20H)写入数据前,检查内存空间是否全部都是 0XFF ,不满足需擦除。拉低CS片选 → 发送20H→ 发送24位地址 → 拉高CS片选W25Q128 状态寄存器W25Q128 一共有 3 个状态寄存器,它们的作用是跟踪芯片的状态。其中,状态寄存器 1 较为常用。BUSY:指示当前的状态,0 表示空闲,1 表示忙碌WEL:写使能锁定,为 1 时,可以操作页/扇区/块。为 0 时,写禁止。W25Q128 常见操作流程以下流程省略了拉低/拉高片选信号CS。读操作:擦除扇区:写操作实验:使用 SPI 通讯读写 W25Q128 模块硬件接线VCC -- 3.3VCS -- PA4CLK -- PA5DO -- PA6DI -- PA7cubeMX配置w25q128_write_nocheck流程图项目六、温湿度LCD显示并上传服务器项目需求使用温湿度传感器模块(DHT11)获取温度及湿度,并将值显示在LCD1602上,同时通过蓝牙模块透传到手机。项目框图硬件清单DHT11LCD1602HC-08继电器杜邦线a. LCD1602介绍及实战硬件接线D0~D7 -- A0~A7RS -- B1RW -- B2EN -- B10V0 -- GND(正视看不到显示结果,需要侧着看。否则需要接可调电阻)引脚封装RS、RW、EN三根信号线经常需要进行拉高/拉低操作,可以进行封装如何将一个字节的数据按位一次性发送到GPIOA的8个管脚?#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)GPIOA->ODR = 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 << 1;tmp |= flag;}datas[i] = tmp;}
}
int fputc(int ch, FILE *f)
{unsigned char temp[1]={ch};HAL_UART_Transmit(&huart1,temp,1,0xffff);return ch;
}
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();项目设计及实现项目设计继电器数据线插在PB6上,DHT11及LCD1602接线与上述相同。项目实现注意点:1. 不要忘记将Use MicroLIB的勾打上;2. 不要忘记在main函数把串口中断打开;3. 使用蓝牙模块时,记得将波特率设置为9600./* 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();/* USER CODE BEGIN 2 */printf("hello world\r\n");HAL_Delay(2000);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 *///DHT_Get_Temp_Humi_Data(datas);Read_Data_From_DHT();printf("Temp: %d.%d ", datas[2], datas[3]);printf("Humi: %d.%d\r\n", datas[0], datas[1]);HAL_Delay(2000);}/* USER CODE END 3 */
}
项目七:智能小车1. 让小车动起来对应源代码:smartCar_project1硬件接线B-1A -- PB0B-1B -- PB1A-1A -- PB2A-1B -- PB10其余接线参考上官一号小车项目。代码实现motor.cchar message[16];
while (1)
{/* USER CODE END WHILE *//* USER CODE BEGIN 3 */Read_Data_From_DHT();if (datas[2] >= 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 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 位置为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);
#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) < ((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.cwhile (1){if(dir != MIDDLE){sgMiddle();dir = MIDDLE;HAL_Delay(300);9.3 封装电机驱动对应源代码:smartCar_project9_3硬件接线与 “让小车动起来” 完全一样B-1A -- PB0B-1B -- PB1A-1A -- PB2A-1B -- PB10代码实现}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();}}
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 < disLeft){goLeft();HAL_Delay(150);stop();}}HAL_Delay(50);
}
cubeMX配置代码实现11. 串口控制小车并使用Oled显示速度对应源代码:smartCar_project11unsigned 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);
硬件接线SCL -- PB6SDA -- PB7封装Oled模块对应源代码:smartCar_project11_1实现测速并使用Oled显示速度对应源代码:smartCar_project11_2代码实现太长了,大家直接去看源代码吧。。12. Wi-Fi测速小车并本地Oled显示对应源代码:smartCar_project12硬件接线把esp8266插进串口113. 语音控制小车对应源代码:smartCar_project13硬件接线循迹小车:循迹模块(左) -- PB3循迹模块(右) -- PB4跟随小车:跟随模块(左) -- PA8跟随模块(右) -- PA9避障小车:sg90:PB9Trig:PA10Echo:PA11OLED模块:SCL -- PB6SDA -- PB7语音模块:A25 -- PA15 (跟随)A26 -- PA13 (避障)A27 -- PA14 (循迹)cubeMX配置SU-03T配置配置与上官一号一样样~注意事项1. 语音模块电源需要接到上官二号,否则小车跟随功能异常;2. 上官二号烧录代码时,需要将语音模块断电,否则烧录失败。代码实现#include "sg90.h"#include "sr04.h"#include "motor.h"#include "oled.h"#include "string.h"#define MIDDLE 0#define LEFT 1#define RIGHT 2#define BZ 1#define XJ 2#define GS 3#define LeftWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3)#define RightWheel_Value_XJ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)#define LeftWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8)#define RightWheel_Value_GS HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)#define XJ_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_14)#define GS_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)#define BZ_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_13)char dir;
void xunjiMode()
{if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ ==GPIO_PIN_RESET)goForward();if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_RESET)goLeft();if(LeftWheel_Value_XJ == GPIO_PIN_RESET && RightWheel_Value_XJ == GPIO_PIN_SET)goRight();if(LeftWheel_Value_XJ == GPIO_PIN_SET && RightWheel_Value_XJ == GPIO_PIN_SET)stop();
}
void gensuiMode()
{if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS ==GPIO_PIN_RESET)goForward();if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_RESET)goRight();if(LeftWheel_Value_GS == GPIO_PIN_RESET && RightWheel_Value_GS == GPIO_PIN_SET)goLeft();if(LeftWheel_Value_GS == GPIO_PIN_SET && RightWheel_Value_GS == GPIO_PIN_SET)stop();
}
void bizhangMode()
{double disMiddle;double disLeft;double disRight;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);
}
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 */
}
相关文章:

STM32——智能小车
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…...

开源:基于Vue3.3 + TS + Vant4 + Vite5 + Pinia + ViewPort适配..搭建的H5移动端开发模板
vue3.3-Mobile-template 基于Vue3.3 TS Vant4 Vite5 Pinia ViewPort适配 Sass Axios封装 vconsole调试工具,搭建的H5移动端开发模板,开箱即用的。 环境要求: Node:16.20.1 pnpm:8.14.0 必须装上安装pnpm,没装的看这篇…...

缩略图保持加密(thumbnail-preserving encryption, TPE)的理论基础
这涉及到一些视觉心理学等方面知识: 1、参考文献: 云存储图像缩略图保持的加密研究进展(中国图像图形学报) 一些视觉心理学的研究为TPE的成功实现提供了理论基础。Potter(1975, 1976)的研究表明人类的视觉系统能够在100 ms内从一个新场景中提取出相应的语义信息;250 ms内…...

nodejs+vue+mysql校园失物招领网站38tp1
本高校失物招领平台是为了提高用户查阅信息的效率和管理人员管理信息的工作效率,可以快速存储大量数据,还有信息检索功能,这大大的满足了用户和管理员这两者的需求。操作简单易懂,合理分析各个模块的功能,尽可能优化界…...

GEDepth:Ground Embedding for Monocular Depth Estimation
参考代码:gedepth 出发点与动机 相机的外参告诉了相机在世界坐标系下的位置信息,那么可以用这个外参构建一个地面基础深度作为先验,后续只需要在这个地面基础深度先验基础上添加offset就可以得到结果深度,这样可以极大简化深度估…...

校园圈子论坛系统--APP小程序H5,前后端源码交付,支持二开!uniAPP+PHP书写!
随着移动互联网的快速发展,校园社交成为了大学生们日常生活中重要的一部分。为了方便校园内学生的交流和互动,校园社交小程序逐渐走入人们的视野。本文将探讨校园社交小程序的开发以及其带来的益处。 校园社交小程序的开发涉及许多技术和设计方面。首先&…...

VMware vCenter告警:vSphere UI运行状况警报
vSphere UI运行状况警报 不会详细显示告警的具体内容,需要我们自己进一步确认告警原因。 vSphere UI运行状况警报是一种监控工具,用于检测vSphere环境中的潜在问题。当警报触发时,通常表示系统遇到了影响性能或可用性的问题。解决vSphere UI…...

C# 引用同一个dll不同版本的程序集
因为项目需要所以必须在项目中引用不同版本的同一程序集 我要引用的文件是newtonsoft.json.dll 两个版本为12.0.0.0 和4.0.0.0 1.如果已经先引入了newtonsoft.json 12.0.0.0版本的程序集,如果直接引入另一个版本的程序集的话会提示不成功,所以先将另一个…...

单机搭建hadoop环境(包括hdfs、yarn、hive)
单机可以搭建伪分布式hadoop环境,用来测试和开发使用,hadoop包括: hdfs服务器 yarn服务器,yarn的前提是hdfs服务器, 在前面两个的基础上,课可以搭建hive服务器,不过hive不属于hadoop的必须部…...

LEETCODE 170. 交易逆序对的总数
class Solution { public:int reversePairs(vector<int>& record) {if(record.size()<1)return 0;//归并 递归int left,right;left0;rightrecord.size()-1;int nummergeSort(left,right,record);return num;}int mergeSort(int left,int right, vector<int>…...

「HarmonyOS」EventHub事件通知详细使用方法
需求背景: 在开发过程中,肯定会出现触发特定事件,需要全局进行通知,与之相关的部分进行执行相应的修改方法。举个例子:修改了用户个人昵称,需要进行全局通知,在涉及昵称的部分收到通知后&#…...

为什么golang不支持可重入锁呢?
为什么golang不需要可重入锁? 在工程中使用锁的原因在于为了保护不变量,也可以用于保护内、外部的不变量。 基于此,Go 在互斥锁设计上会遵守这几个原则。如下: 在调用 mutex.Lock 方法时,要保证这些变量的不变性保持…...

聊一聊Tomcat的架构和运行流程,尽量通俗易懂一点
1、Tomcat的架构 这里可以看出 A、一个Tomcat就是一个Server,一个Server下会有多个Service, B、Service只负责封装多个Connector和一个Container(Service本身不是容器,可以看做只是用来包装Connector和Container的壳,…...

ModelArts加速识别,助力新零售电商业务功能的实现
前言 如果说为客户提供最好的商品是产品眼中零售的本质,那么用户的思维是什么呢? 在用户眼中,极致的服务体验与优质的商品同等重要。 企业想要满足上面两项服务,关键在于提升效率,也就是需要有更高效率的零售&#…...

Qt/C++音视频开发65-切换声卡/选择音频输出设备/播放到不同的声音设备/声卡下拉框
一、前言 近期收到一个用户需求,要求音视频组件能够切换声卡,首先要在vlc上实现,于是马不停蹄的研究起来,马上查阅对应vlc有没有自带的api接口,查看接口前,先打开vlc播放器,看下能不能切换&…...

MySQL原理(一)架构组成之逻辑模块(1)组成
总的来说,MySQL可以看成是二层架构,第一层我们通常叫做SQL Layer,在MySQL数据库系统处理底层数据之前的所有工作都是在这一层完成的,包括权限判断,sql解析,执行计划优化,query cache的处理等等&…...

一、cadence PDK 自学笔记-心法
我这边ADS /Cadence PDK基本大部分都是自学完成的。 当然也非常感谢我的前同事周**的帮忙,教了我很多基础的。另外也感谢我现在同事,李**和程*的帮忙,学习了很多cad的视角。 其实对于自学写PDK的小伙伴,一般都要如何学习呢&…...

防御保护--NAT策略
目录 NAT策略 NAT类型 server-map表 P2P --- peer to peer 网络类型 编辑 目标NAT--服务器映射 双向NAT 编辑 多出口NAT NAT策略 静态NAT --- 一对一 动态NAT --- 多对多 NAPT --- 一对多的NAPT --- easy ip --- 多对多NAPT 服务器映射 源NAT--基于源IP地址进行转…...

【C++】C++入门 — 指针空值nullptr
C入门 指针空值 指针空值 在良好的C/C编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现 不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下 方式对其进行初始化: voi…...

Vue3+Koa2实现图片上传(不再畏惧)
大家好,我是勇宝,一个热爱前端的小学生,年关将至,提前祝大家新年快乐。今天呢,我们就来好好的啃一啃图片上传,从一个前端开发者的角度来探讨一下图片上传前后端到底都做了哪些事情。 文章目录 一、技术摘要…...

wsl-ubuntu 安装 nginx
wsl-ubuntu 安装 nginx 1. 安装 nginx2. 确认 nginx 启动状态3. 重启 nginx4. 停止 nginx 1. 安装 nginx sudo apt install nginx2. 确认 nginx 启动状态 systemctl status nginx3. 重启 nginx systemctl restart nginx4. 停止 nginx systemctl stop nginx完成!…...

重学Ajax
摘要:AJAX是一个在前端的应用非常广泛技术,为什么还要谈它呢?么得办法之前学的不全面,再收拾收拾。水平有限,欢迎指正! AJAX(全称:Asynchronous JavaScript and XML)是一…...

springboot3+vue3支付宝交易案例-结算支付
springboot3vue3支付宝交易案例-结算支付!今天下午整理了一下结算的内容。遇到了很多问题。汇总分享给大家。 第一个问题:支付宝结算后,返回的交易编码,和交易时间,交易状态,都应该使用varchar来存。 第二…...

c语言 ceil() 函数
ceil()是C语言中的一个数学函数,用于向上取整。它的函数原型定义在math.h头文件中。 ceil()函数的作用是返回一个大于或等于给定参数的最小整数值,即将参数向上取整到最接近的整数。返回值的数据类型为double。 以下是ceil()函数的函数原型:…...

virtualBox虚拟机安装ubuntu后的必要配置
1. 使能双向copy 粘贴功能。在device menu的 shared clipboard项, 选bidirectional. 2.启用共享文件夹。 在device 菜单的 shared folder 项配置, (对于日期乱码问题和命令行打不开的问题请见ubuntu18.04安装后时间日期乱码及terminal打不开解决方法_电脑日期变成…...

《Pandas 简易速速上手小册》第6章:Pandas 时间序列分析(2024 最新版)
文章目录 6.1 时间序列数据基础6.1.1 基础知识6.1.2 重点案例:股票市场分析6.1.3 拓展案例一:温度变化分析6.1.4 拓展案例二:电商平台日销售额分析 6.2 日期与时间功能6.2.1 基础知识6.2.2 重点案例:活动日志分析6.2.3 拓展案例一…...

滇西科技师范学院食堂大宗物资采购项目(冰冻制品类)招标公告
滇西科技师范学院食堂大宗物资采购项目(冰冻制品类)招标公告 (招标编号:YDZOH20240158) 项目所在地区:云南省,临沧市,市辖区 一、招标条件 本滇西科技师范学院食堂大宗物资采购项目(冰冻制品类)已由项目审批/核准/备案机关批准,项目资金来源为…...

(2024,SaFaRI,双三上采样和 DFT,空间特征和频率特征)基于扩散模型的图像空间和频率感知恢复方法
Spatial-and-Frequency-aware Restoration method for Images based on Diffusion Models 公和众和号:EDPJ(进 Q 交流群:922230617 或加 VX:CV_EDPJ 进 V 交流群) 目录 0. 摘要 3. 方法 3.1 修改数据保真度 3.2 …...

【Linux】环境基础开发工具的使用之gcc详解(二)
前言:上一篇文章中我们讲解了Linux下的vim和yum的工具的使用,今天我们将在上一次的基础上进一步的讲解开放工具的时候。 💖 博主CSDN主页:卫卫卫的个人主页 💞 👉 专栏分类:Linux的深度刨析 👈 Ὂ…...

go语言-用channel控制goroutine的退出
用channel控制goroutine的退出 本文简要介绍了,如何用channel控制goroutine的退出的基本方法 for-range主动停止goruitine package mainimport ("fmt""sync""time" )/* Go并发编程模型:主动停止goroutine 方法一&#…...