赞
踩
LED1 PB8 LED2 PB9
KET1 PA0 KEY2 PA1
TXD1 PA9 RXD1 PA10
TXD2 PA2 RXD2 PA3
TXD3 B10 RXD3 B11
SCL PA6 SDA PA7
这里有一篇好文章,感谢博主,讲的很详细。
——————————————————————————————————————————
STM32 CubeMx教程 -- 基础知识及配置使用教程_stm32cubemx教程_Dir_xr的博客-CSDN博客
——————————————————————————————————————————
下面我们以轮询法点灯来演示一下,首先看原理图,知道LED和KEY的接线
LED1 PB8 LED2 PB9 低电平亮
KET1 PA0 KEY2 PA1 低电平代表被按下
1、打开软件点击箭头所指的按钮
2、在箭头所指的地方输入自己的芯片,然后双击第二个箭头自己的芯片型号
3、配置SYS参数
4、配置引脚参数,直接点上面的引脚选择不同的功能,PB8、PB9配置成输出模式,PA0、PA1配置为输入模式(检测这两个引脚的值来判断按键是否被按下)。
5、在外设的详细配置界面配置下PB8、PB9的初始值为高电平(这一步可要可不要),区别就是上电以后LED默认是亮还是不亮的问题。
6、生成工程文件,填好自己的项目名字和保存位置,IDE选择MDK-ARM,然后按照下面打勾,最后生成文件即可
7、此时已经自动打开了keil软件了,就可以进行代码的编写了
8、轮询法点亮LED代码
把下面的代码放到主函数的while(1)循环中即可
- if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==RESET){
- HAL_Delay(20);
- while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==RESET);
- HAL_Delay(20);
- HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
- }
- if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==RESET){
- HAL_Delay(20);
- while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==RESET);
- HAL_Delay(20);
- HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
LED1 PB8 LED2 PB9 低电平亮
KEY1 PA0 KEY2 PA1 低电平代表被按下
按照上面的步骤配置,区别是要配置RCC,选择外部晶振,时钟树配置成72按确定
然后在GPIO配置的时候PA0、PA1选择中断模式,然后配置成下降沿触发中断
在NVIC打开中断
然后配置项目文件打开Keil编程即可。
要使用中断我们要知道怎么去找中断服务函数,找到以后重写虚函数。
接下来我们重写虚函数,把虚函数的定义复制到主函数前重新写
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
注意:中断里面尽量不要用HAL_Delay这个函数,应为这这个函数本身也是靠Systick定时器中断来完成的,而且这个中断的优先级一般是最低的,在GPIO的中断回调函数中得不到响应额,就会锁死在这个函数这里。
这里不能把延时函数写在case语句下面,程序会卡死。(原因不知道)。
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
- {
- switch(GPIO_Pin){
- HAL_Delay(20);
- case GPIO_PIN_0:
- while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET);
- HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
- break;
- case GPIO_PIN_1:
- while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1)==GPIO_PIN_RESET);
- HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
- break;
- }
- }
基本定时器 TIM6~TIM7
通用定时器 TIM2~TIM5
高级定时器 TIM1和TIM8
STM32F103C8T6有四个定时器
一个高级定时器TIM1 三个通用定时器TIM2 TIM3 TIM4
向上计数,向下计数,中心对齐计数
要求:使用定时器中断的方法,每500ms翻转一次LED1的状态
我们使用TIM2来完成
1、打开CubeMX配置gpio口PB8、PB9为输出模式
2、配置RCC为外部高速时钟并且时钟树配置为72MHz
3、配置TIM2和使能定时器中断
4、上述做完以后配置好文件名等打开KEIL软件写代码
同样先去中断文件里面找定时器中断的回调函数,重写该函数,函数嵌套的的比较多,这里复制出来了,就不用去自己找了,复制以后,重写即可
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
5、代码
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)回调函数的形参是一个结构体
在写回调函数的时候判断htim->Instance的值来确定是那个定时器中断调用的中断回调函数
在主函数中定时器初始化函数以后,需要添加HAL_TIM_Base_Start_IT(&htim2);函数启动定时器中断
重写中断回调函数
写好以后编译下载即可。
在51中是没有硬件支持的,需要我们用软件模拟,在32中资源丰富,有硬件电路支持。
PWM的周期和频率、占空比
实操
需求:使用PWM点亮LED1实现呼吸灯。
首先看原理图和STM32使用手册
LED接到PB8的,同时PB8又跟TIM4的CH3复用一根线。所以我们待会配置PB8的时候要配置成TIM4_CH3。
打开CubeMX开始配置
第五步开始配置TIM4(因为PB8接到TIM4的CH3的)
根据上图配置完以后,完成后面的项目配置(项目名称之类的)打开Keil。
首先介绍两个函数
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
参数:1、启动的那个定时器 2、启用的哪一路PWM
在主函数初始化PWM以后要调用这个函数
__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
完整代码如下:
- 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 */
- uint16_t pwmVal=0;
- uint8_t flag=1;//状态标志位 1 越来越亮 0 越来越暗
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_TIM4_Init();
- /* USER CODE BEGIN 2 */
- HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3);
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- HAL_Delay(1);
- /* USER CODE END WHILE */
- if(pwmVal>500){
- flag=0;
- }
- if(pwmVal==0){
- flag=1;
- }
- if(flag==1){
- pwmVal++;
- }
- else if(flag==0){
- pwmVal--;
- }
- __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }

需求:每隔1s,转动一个角度:0度 --> 45度 --> 90度 --> 135度 --> 180度 --> 0度
原理:舵机由三根线,vcc、gnd、信号线、向黄色的信号线输入不同的PWM波形就可以控制舵机的转向了。
向黄色信号线“灌入”PWM信号
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
本次选用TIM3_CH1来产生PWM波形,查看手册是PA6引脚。
1、打开CubeMx开始配置
2、接着配置工程文件,这里就不演示了,配置完成打开keil开始写代码
老规律,这么配置完,主函数里面只有对TIM3的初始化,要想使用PWM还得添加启动函数
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_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_TIM3_Init();
- /* USER CODE BEGIN 2 */
- HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);//PWM启动函数
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- //__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwmVal);
- uint16_t pwmVal=5;
- while (1)
- {
- /* USER CODE END WHILE */
- __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwmVal);//改变CCRx的值
- HAL_Delay(1000);
- pwmVal=pwmVal+5;
- if(pwmVal>=30){
- pwmVal=5;
- }
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }

需求:
使用超声波测距,当手离传感器距离小于5cm时,LED1点亮,否则保持不亮状态。
超声波模块的原理如下:
怎么让它发送波
Trig ,给Trig端口至少10us的高电平
怎么知道它开始发了
Echo信号,由低电平跳转到高电平,表示开始发送波
怎么知道接收了返回波
Echo,由高电平跳转回低电平,表示波回来了
怎么算时间
Echo引脚维持高电平的时间!
波发出去的那一下,开始启动定时器
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
怎么算距离
距离 = 速度 (340m/s)* 时间/2
这个练习需要自己手写一个延时10us的函数,因为STM32的HAL_Delay函数最低延时就是1ms。
这里使用TIM2来完成这个延时的函数
定时器配置:
使用 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、打开CubeMx开始配置
SYS RCC 时钟树 配置定时器 GPIO口 PB6输出 PB7输入
2、代码
- 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_TIM2_Init();
- /* USER CODE BEGIN 2 */
-
- int cnt;
- float distance;
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- // 怎么让它发送波
- //Trig ,给Trig端口至少10us的高电平
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
- TIM2_Delay_us(15);
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
- //怎么知道它开始发了
- //Echo信号,由低电平跳转到高电平,表示开始发送波
- //波发出去的那一下,开始启动定时器
- while( HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_RESET);
- HAL_TIM_Base_Start(&htim2);
- __HAL_TIM_SetCounter(&htim2,0);//让定时器从0开始计数
- //怎么知道接收了返回波
- //Echo,由高电平跳转回低电平,表示波回来了
- //波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
- while( HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)==GPIO_PIN_SET);
- HAL_TIM_Base_Stop(&htim2);
- //怎么算时间
- //Echo引脚维持高电平的时间!
- cnt=__HAL_TIM_GetCounter(&htim2);//读取定时的数值
- //怎么算距离
- //距离 = 速度 (340m/s)* 时间/2
- distance=cnt*340/2*0.000001*100;
- if(distance<5){
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
- }
- else{
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
- }
- HAL_Delay(100);
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }

HAL_UART_Transmit(); 串口发送数据,使用超时管理机制
HAL_UART_Receive(); 串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT(); 串口中断模式发送
HAL_UART_Receive_IT(); 串口中断模式接收
- HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
- uint8_t *pData, uint16_t Size, uint32_t Timeout)
作用:以阻塞的方式发送指定字节的数据
形参 1 :UART_HandleTypeDef 结构体类型指针变量
形参 2:指向要发送的数据地址
形参 3:要发送的数据大小,以字节为单位
形参 4:设置的超时时间,以ms单位
- HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,
- uint8_t *pData, uint16_t Size)
作用:以中断的方式接收指定字节的数据
形参 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_STA(这个变量的名字可以随便取)可以定义为16位的也可以定义位32位的,看自己的需求。
从0开始,串口中断接收到一个数据(一个字节)就自增1。当数据读取全部OK时候(回车和换行
符号来的时候),那么 USART_RX_STA的最高位置1,表示串口数据接收全部完毕了,然后main
函数里面可以处理数据了
串口中断流程
1、配置CubeMX,把串口工具接到开发板查到电脑上
2、打开keil开始写代码
在main函数前需要重写一下fputc函数,然后打开魔术棒勾选MicroLIB。
- 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 */
- unsigned char str[20]={'\0'};
- /* 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();
- /* USER CODE BEGIN 2 */
- // HAL_UART_Transmit(&huart1,"hello world\n",strlen("hello world\n"),100);
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- HAL_UART_Receive(&huart1,str,19,100);
- printf("%s",str);//要使用printf打印到串口 需要重写fputc函数,然后勾选魔术棒的MicroLIB
- memset(str,'\0',strlen(str));
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }

接线方式同上,配置同上,这里需要打开中断
代码
- #include "main.h"
- #include "usart.h"
- #include "gpio.h"
- #include <stdio.h>
- #include <string.h>
-
- uint8_t buf=0; //串口接收缓存 一字节
- #define UART_REC_LEN 200 //最大接收字节数
- uint8_t UART1_REC_BUFF[UART_REC_LEN]; //接收缓冲
-
- int fputc(int ch,FILE *f) //printf重定向
- {
- unsigned char temp[1]={ch};
- HAL_UART_Transmit(&huart1,temp,1,0xffff);
- return ch;
- }
- void SystemClock_Config(void);
-
- 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 位置为1
- UART1_RX_STA |= 0x8000;
- else
- // 否则认为接收错误,重新开始
- UART1_RX_STA = 0;
- }
- else // 如果没有收到了 0x0d (回车)
- {
- //则先判断收到的这个字符是否是 0x0d (回车)
- if(buf == 0x0d)
- {
- // 是的话则将 bit14 位置为1
- UART1_RX_STA |= 0x4000;
- }
- else
- {
- // 否则将接收到的数据保存在缓存数组里
- UART1_REC_BUFF[UART1_RX_STA & 0X3FFF] = buf;
- UART1_RX_STA++;// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
- if(UART1_RX_STA > UART_REC_LEN - 1)
- UART1_RX_STA = 0;
- }
- }
- }// 重新开启中断
- HAL_UART_Receive_IT(&huart1, &buf, 1);
- }
- }
-
- int main(void)
- {
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_USART1_UART_Init();
- HAL_UART_Receive_IT(&huart1, &buf, 1);
- while (1)
- {
- if(UART1_RX_STA & 0x8000)
- {
- printf("收到数据:");
- // 将收到的数据发送到串口
- HAL_UART_Transmit(&huart1, UART1_REC_BUFF, UART1_RX_STA & 0x3fff, 0xffff);
- // 等待发送完成
- while(huart1.gState != HAL_UART_STATE_READY);
- printf("\r\n");
- // 重新开始下一次接收
- UART1_RX_STA = 0;
- }
- }
- }

简介:
在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗(watchdog) 。
独立看门狗工作在主程序之外,能够完全独立工作,它的时钟是专用的低速时钟(LSI),由
VDD 电压供电, 在停止模式和待机模式下仍能工作。
独立看门狗本质
本质是一个 12 位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即 IWDG_RESET 。
如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗
独立看门狗框图
独立看门狗时钟
独立看门狗的时钟由独立的RC振荡器LSI提供,即使主时钟发生故障它仍然有效,非常独立。启用IWDG后,LSI时钟会自动开启。LSI时钟频率并不精确,F1用40kHz。
LSI经过一个8位的预分频器得到计数器时钟。
预分频寄存器(IWDG_PR)
重装载寄存器
重装载寄存器是一个12位的寄存器,用于存放重装载值,低12位有效,即最大值为4096,这个值的大小决定着独立看门狗的溢出时间。
键寄存器
键寄存器IWDG_KR可以说是独立看门狗的一个控制寄存器,主要有三种控制方式,往这个寄存器写入下面三个不同的值有不同的效果。
1、打开CubeMX开始配置
2、开始写代码
HAL_IWDG_Refresh(&hiwdg); //喂狗
没有按下KEY1喂狗的现象,每隔一秒程序会重新从头开始运行,LED会闪烁。
- #include "main.h"
- #include "iwdg.h"
- #include "usart.h"
- #include "gpio.h"
- void SystemClock_Config(void);
- int main(void)
- {
-
- HAL_Init();
-
-
- SystemClock_Config();
-
-
- MX_GPIO_Init();
- MX_IWDG_Init();
- MX_USART1_UART_Init();
-
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
- HAL_Delay(100);
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
- while (1)
- {
- if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET){
- HAL_Delay(20);
- while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET);
- HAL_Delay(20);
- HAL_IWDG_Refresh(&hiwdg); //喂狗
- }
- }
- }

简介:
窗口看门狗用于监测单片机程序运行时效是否精准,主要检测软件异常,一般用于需要精准检测程序运行时间的场合。
窗口看门狗的本质
是一个能产生系统复位信号和提前唤醒中断的6位计数器
产生复位条件:
当递减计数器值从 0x40 减到 0x3F 时复位(即T6位跳变到0)
计数器的值大于 W[6:0] 值时喂狗会复位。
产生中断条件:
当递减计数器等于 0x40 时可产生提前唤醒中断 (EWI)。
在窗口期内重装载计数器的值,防止复位,也就是所谓的喂狗。
WWDG工作原理
WWDG框图
控制寄存器WWDG_CR
配置寄存器WWDG_CFR
状态寄存器WWDG_SR
超时时间计算
1、配置CubeMX
2、写代码
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
HAL_WWDG_Refresh(hwwdg);
代码
- #include "main.h"
- #include "wwdg.h"
- #include "gpio.h"
- void SystemClock_Config(void);
- void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)//提前中断回调函数
- {
- HAL_WWDG_Refresh(hwwdg);//喂狗
- HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
- }
- int main(void)
- {
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
- HAL_Delay(300);
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
- MX_WWDG_Init();
- while (1)
- {
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
- HAL_Delay(40);
- }
- }

简介:
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的内容打印到屏幕。
1、配置CubeMX
2、编写代码
- HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t
- DstAddress, uint32_t DataLength)
作用:用于启动一个DMA(Direct Memory Access)传输
参数一:DMA_HandleTypeDef *hdma,DMA通道句柄
参数二:uint32_t SrcAddress,源内存地址
参数三:uint32_t DstAddress,目标内存地址
参数四:uint32_t DataLength,传输数据长度。注意:需要乘以sizeof(uint32_t)
返回值:HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT)
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__)
作用:用于获取DMA(Direct Memory Access)控制器中的特定标志位状态。
参数一:HANDLE,DMA通道句柄
参数二:FLAG,数据传输标志。DMA_FLAG_TCx表示数据传输完成标志
返回值:FLAG的值(SET/RESET)
代码实现
1、开启数据传输
2、等待数据传输完成
3、打印数组内容
- #include "main.h"
- #include "dma.h"
- #include "usart.h"
- #include "gpio.h"
- #include <stdio.h>
- #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];
-
- void SystemClock_Config(void);
-
- int fputc(int ch, FILE *f)
- {
- unsigned char temp[1]={ch};
- HAL_UART_Transmit(&huart1,temp,1,0xffff);
- return ch;
- }
-
- int main(void)
- {
- int i;
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_DMA_Init();
- MX_USART1_UART_Init();
- 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]);
- }
- while (1)
- {
-
- }
-
- }

需求:使用DMA的方式将内存数据搬运到串口1发送寄存器,同时闪烁LED1。
1、配置CubeMX
2、写代码
- HAL_StatusTypeDef HAL_UART_Transmit_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)
代码实现
1. 准备数据
2. 将数据通过串口DMA发送
- #include "main.h"
- #include "dma.h"
- #include "usart.h"
- #include "gpio.h"
-
- #define BUF_SIZE 1000
- unsigned char BUF[BUF_SIZE];
- void SystemClock_Config(void);
-
- int main(void)
- {
- int i;
- for(i=0;i<1000;i++){
- BUF[i]='A';
- }
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_DMA_Init();
- MX_USART1_UART_Init();
- HAL_UART_Transmit_DMA(&huart1,BUF,BUF_SIZE);
- while (1)
- {
-
- }
- }

需求:使用DMA的方式将串口接收缓存寄存器的值搬运到内存中,在从内存发送到串口,同时闪烁LED1。
常用的函数
__HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)
参数1:指向UART设备的指针
参数2:中断类型
在STM32的HAL库中,UART设备可以使用多种中断,例如:
UART_IT_TX
: 发送中断UART_IT_RX
: 接收中断UART_IT_TC
: 发送完成中断UART_IT_RCF
: 接收完成中断UART_IT_IDLE
: 空闲中断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)
- __HAL_DMA_GET_COUNTER
- #define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR
作用:获取DMA通道的计数值。具体而言,它访问了DMA通道的CNDTR
寄存器,该寄存器存储了当前传输的字节数。通过这个宏定义,可以方便地获取DMA通道的计数值,以便在DMA传输过程中进行监控和管理。
参数一:HANDLE,串口句柄
返回值:未传输数据大小
__HAL_UART_GET_FLAG(__HANDLE__, __FLAG__)
参数一:HANDLE,串口句柄
参数二:FLAG,需要查看的FLAG
在STM32的HAL库中,UART设备的标志位包括:
UART_FLAG_TXE
:发送寄存器空标志UART_FLAG_TC
:发送完成标志UART_FLAG_RXNE
:接收寄存器非空标志UART_FLAG_IDLE
:空闲线标志UART_FLAG_ERR
:错误标志返回值:返回值是一个布尔值(bool),表示指定的标志位是否被设置。如果指定的标志位被设置,那么返回值为真(true);否则,返回值为假(false)。
__HAL_UART_CLEAR_IDLEFLAG(__HANDLE__)
参数一:HANDLE,串口句柄
返回值:无
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数:huart,串口指针。
功能:停止DMA的传输。
1、打开CubeMX函数配置
需要注意的是,DMA2支持支内存到内存的传输,所以练习1用的是DMA2,而练习2,练习3是内存到外设和外设到内存,所以这两个练习使用的是DMA1。
2、写代码
main.c
- #include "main.h"
- #include "dma.h"
- #include "usart.h"
- #include "gpio.h"
- #define BUF_SIZE 100
- uint8_t rcvBuf[BUF_SIZE]; // 接收数据缓存数组
- uint8_t rcvLen = 0; // 接收一帧数据的长度
- void SystemClock_Config(void);
- int main(void)
- {
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_DMA_Init();
- MX_USART1_UART_Init();
- __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);
- }
- }

stm32f1xx_it.c
- #define BUF_SIZE 100
- extern uint8_t rcvBuf[BUF_SIZE];
- extern uint8_t rcvLen;
- void USART1_IRQHandler(void)
- {
- HAL_UART_IRQHandler(&huart1);
- 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
- }
- }

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 位数据寄存器中
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个周期
12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能
是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。
ADC转化模式
扫描模式
关闭扫描模式:只转换ADC_SQRx或ADC_JSQR选中的第一个通道
打开扫描模式:扫描所有被ADC_SQRx或ADC_JSQR选中的所有通道
单次转换/连续转换
单次转换:只转换一次
连续转换:转换一次之后,立马进行下一次转换
烟雾报警器接到PA0口
代码
- 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);
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。