精华内容
下载资源
问答
  • 包含STM32CubeMX中文使用手册STM32_f1的hal库,开发者可以使用两者共同开发,同步学习cube和hal
  • STM32的库函数使用手册,标准库的使用手册中文的建议还是看英文版,HAL库的使用手册是英文版的 没找到中文
  • 本篇文章介绍如何使用STM32HAL库,针对芯片读写保护实现防篡改、破解功能(详解),实质也是操作Flash。 硬件:STM32F103CBT6最小系统板 软件:Keil 5.29+ STM32CubeMX5.6.1 一、使用方法 通过参阅《STM32...

    目录

     

    概述

    一、使用方法

    二、STM32CubeMx配置​

    三、Examples

    四、运行结果

    五、总结


    概述

        本篇文章介绍如何使用STM32HAL库,针对芯片读写保护实现防篡改、破解功能(详解),本案例还包含内部FLASH读写数据,本质就是操作Flash。

    硬件:STM32F103CBT6最小系统板
    软件:Keil 5.29  + STM32CubeMX5.6.1
     

    一、使用方法

              通过参阅《STM32中文参考手册》得知,不同型号的芯片对应FLASH大小不一样,如下所示:

    在《STM32中文参考手册》pdf文档中找到,第2.3.3小节:嵌入式闪存,对应的页数30。

    这里我使用的是STM32F103CBT6的FLASH是128k,通过手册得知是属于中容量,所以只需看下面FLASH地址分配图与每一页对应的大小(字节)即可。

    想更详细的了解,请阅读《STM32中文参考手册》,网上大把可以下载,这里就给出此文档的下载链接了。

    二、STM32CubeMx配置

    三、Examples

    打开STM32CubeMx生成的keil工程,新建bsp文件夹,同时分别新建bsp_flash.c与bsp_flash.h文件,并把这两个文件,添加keil工程中来即可。

    添加头文件路径

    1、bsp_flash.h文件

    #ifndef __BSP_FLASH_H
    #define __BSP_FLASH_H
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    
    
    /* Includes ------------------------------------------------------------------*/
    #include "stm32f1xx_hal.h"
    	
    
    
    #define  DATASTOREADDR 	0x0800F800//数据存放地址	
    
    
    /* Error code */ 
    typedef enum 
    {
      FLASHIF_OK = 0,
      FLASHIF_ERASEKO,
      FLASHIF_WRITINGCTRL_ERROR,
      FLASHIF_WRITING_ERROR,
      FLASHIF_PROTECTION_ERRROR
    }Flash_Status_t;
    
    
    typedef enum {
    	APPLICATION_NORMAL = 0,		// APP能正常稳定运行
    	APPLICATION_UPDATED,			// APP刚更新完成,等待测试启动
    	APPLICATION_ERROR,				// APP错误,不能正常工作
    }Application_Status_t;
    
    typedef	__PACKED_STRUCT{
    	uint32_t Device_id;								// 设备号
    	uint32_t Hardware_Version;				// 硬件版本信息
    	uint32_t Application_Version;		  // APP软件版本
    	uint32_t Application_Status;		  // APP的状态. @Application_Status_t
    	uint32_t SystemParamCRC;
    }SystemParamTypeDef;
    
    
    void Flash_Test(void);
    
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif
    
    

    2、bsp_flash.c文件
     

    #include "bsp_flash.h"
    #include "stdio.h"
    
    
    HAL_StatusTypeDef FLASH_If_EnableReadProtection(void)
    {
    	FLASH_OBProgramInitTypeDef OptionsBytesStruct = { 0 };
    	HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct);
    	HAL_StatusTypeDef result = HAL_OK;
    	
    	if (OptionsBytesStruct.RDPLevel == OB_RDP_LEVEL_0)
    	{
    		OptionsBytesStruct.OptionType = OPTIONBYTE_RDP;
    		OptionsBytesStruct.RDPLevel = OB_RDP_LEVEL_1;
    
    		HAL_FLASH_Unlock();
    		HAL_FLASH_OB_Unlock();
    
    		if (HAL_FLASHEx_OBProgram(&OptionsBytesStruct) != HAL_OK)
    		{
    			HAL_FLASH_OB_Lock();
    			HAL_FLASH_Lock();
    			return result;
    		}
    
    		HAL_FLASH_OB_Lock();
    		HAL_FLASH_Lock();
    		result = HAL_ERROR;
    	}
    	return result;
    }
    
    
    HAL_StatusTypeDef Flash_If_DisableReadProtection(void)
    {
    	FLASH_OBProgramInitTypeDef OptionsBytesStruct = { 0 };
    	HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct);
    	HAL_StatusTypeDef result = HAL_OK;
    	
      if (OptionsBytesStruct.RDPLevel == OB_RDP_LEVEL_1)
    	{
    		OptionsBytesStruct.OptionType = OPTIONBYTE_RDP;
    		OptionsBytesStruct.RDPLevel = OB_RDP_LEVEL_0;
    
    		HAL_FLASH_Unlock();
    		HAL_FLASH_OB_Unlock();
    
    		if (HAL_FLASHEx_OBProgram(&OptionsBytesStruct) != HAL_OK)
    		{
    			HAL_FLASH_OB_Lock();
    			HAL_FLASH_Lock();
    			return result;
    		}
    
    		HAL_FLASH_OB_Lock();
    		HAL_FLASH_Lock();
    		result = HAL_ERROR;
    	}
    	return result;
    }
    
    HAL_StatusTypeDef Set_FlashReadProtection(uint8_t mode)
    {
    	HAL_StatusTypeDef result = HAL_ERROR;
    	
    	if (mode == 1)
    	{
    		FLASH_If_EnableReadProtection();
    		printf("EnableReadProtection!!!\r\n");
    		result = HAL_OK;
    	}
    	else
    	{
    		Flash_If_DisableReadProtection();
    		printf("DisableReadProtection!!!\r\n");
    		result = HAL_OK;
    	}
    	return result;
    }
    
    
    void FLASH_If_Init(void)
    {
      /* Unlock the Program memory */
      HAL_FLASH_Unlock();
    
      /* Clear all FLASH flags */
      __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
    //  /* Unlock the Program memory */
      HAL_FLASH_Lock();
    }
    
    
    uint8_t FLASH_If_Write(uint32_t WriteAddr, uint32_t * pBuffer, uint32_t length)
    {
    	static FLASH_EraseInitTypeDef pEraseInit;
    	uint32_t pageerror = 0;  //定义擦除出错时,返回的Flash地址
    	//uint32_t* pDATA = (uint32_t*)&pBuffer;  
    //	uint8_t page = sizeof(pBuffer) / sizeof(uint16_t);
    	
    	HAL_FLASH_Unlock();
    	pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
    	pEraseInit.PageAddress = WriteAddr;  //定义数据地址
    	pEraseInit.NbPages = 1;  //定义擦除页面数 - 页 = 1024byte
    //	pEraseInit.Banks = FLASH_BANK_1;
    	HAL_FLASHEx_Erase(&pEraseInit, &pageerror);  //擦除Flash
    	
    	for (uint32_t i = 0; i < length; i++)
    	{
    		//HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, WriteAddr + i * 2, *(pDATA + i));	//半字节:FLASH_TYPEPROGRAM_HALFWORD
    #if 0		
    		HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr + i * 4, *(pBuffer + i)); //字节:FLASH_TYPEPROGRAM_WORD
    #else		
    		if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr, *(uint32_t*)(pBuffer+i)) == HAL_OK)      
        {
         /* Check the written value */
          if (*(uint32_t*)WriteAddr != *(uint32_t*)(pBuffer+i))
          {
        	  HAL_FLASH_Lock();
            /* Flash content doesn't match SRAM content */
            return(FLASHIF_WRITINGCTRL_ERROR);
          }
          /* Increment FLASH destination address */
          WriteAddr += 4;
        }
        else
        {
          HAL_FLASH_Lock();
          /* Error occurred while writing data in Flash memory */
          return (FLASHIF_WRITING_ERROR);
        }		
    #endif
    	}
    	HAL_FLASH_Lock();
    	return (FLASHIF_OK);
    }
    
    uint8_t FLASH_If_Read(uint32_t ReadAddr, uint32_t *pBuffer, uint32_t DataLength)
    {
    	uint32_t i;
    	
    	for(i = 0; i < DataLength; i++)
    	{
    		pBuffer[i] = *(__IO uint32_t*)(ReadAddr);//读取4个字节.		
    		ReadAddr += 4;//偏移4个字节.	
    	}
    	return (FLASHIF_OK);
    }
    
    
    void Flash_Test(void)
    {
    	
    	SystemParamTypeDef SystemParam_default = {
    	  .Device_id						= 0x00000111,
    		.Hardware_Version 		= 0x10000111,
    		.Application_Version 	= 0x00000111,
    		.Application_Status 	= APPLICATION_NORMAL
      };
    
    
    	uint32_t page = sizeof(SystemParam_default) / 4;
    	
    	printf("Flash_Test!!!\r\n");
    	Set_FlashReadProtection(1);
    	
    	FLASH_If_Init();
    	
    	//memset(SystemParam_default, 0, sizeof(SystemParam_default));	//清空结构体内容
    	FLASH_If_Write(DATASTOREADDR, (uint32_t*)&SystemParam_default, page);
    	FLASH_If_Read(DATASTOREADDR, (uint32_t*)&SystemParam_default, page);
    	
    	printf("Device_id:0x%08lX, Hardware_Version:0x%08lX, Application_Version:0x%08lX, Application_Status:0x%08lX \r\n",
    						SystemParam_default.Device_id,           SystemParam_default.Hardware_Version,
    						SystemParam_default.Application_Version, SystemParam_default.Application_Status);
    	HAL_Delay(1000);
    
    	printf("UPDATA!!! \r\n");
    	
    	SystemParamTypeDef SystemParam_updata = {
    	  .Device_id						= 0x00000222,
    		.Hardware_Version 		= 0x10000222,
    		.Application_Version 	= 0x10000222,
    		.Application_Status 	= APPLICATION_UPDATED
      };
    
    	FLASH_If_Write(DATASTOREADDR, (uint32_t*)&SystemParam_updata, page);
    	FLASH_If_Read(DATASTOREADDR, (uint32_t*)&SystemParam_updata, page);	
    	printf("Device_id:0x%08lX, Hardware_Version:0x%08lX, Application_Version:0x%08lX, Application_Status:0x%08lX \r\n",
    						SystemParam_updata.Device_id, 					 SystemParam_updata.Hardware_Version,
    						SystemParam_updata.Application_Version, SystemParam_updata.Application_Status);
    	HAL_Delay(1000);					
    }
    
    
    

    3、mian.c文件

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
      * All rights reserved.</center></h2>
      *
      * This software component is licensed by ST under BSD 3-Clause license,
      * the "License"; You may not use this file except in compliance with the
      * License. You may obtain a copy of the License at:
      *                        opensource.org/licenses/BSD-3-Clause
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "stdio.h"
    #include "bsp_flash.h"
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    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();
      /* USER CODE BEGIN 2 */
    	
    	Flash_Test();
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
    		HAL_Delay(1000);
    		HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    #ifdef __GNUC__
      /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
         set to 'Yes') calls __io_putchar() */
      #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    #else
      #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    #endif /* __GNUC__ */
    /**
      * @brief  Retargets the C library printf function to the USART.
      * @param  None
      * @retval None
      */
    PUTCHAR_PROTOTYPE
    {
      /* Place your implementation of fputc here */
      /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
      HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
     
      return ch;
    }
     
    int fgetc(FILE * f)
    {
      uint8_t ch = 0;
      HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 0xffff);
      return ch;
    }
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
    
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
    

     

    四、运行结果


    打开读保护

    只需在bsp_flash.c文件,修改这语句即可 Set_FlashReadProtection(1);
    保存,编译,下载程序运行,运行结果:

    再次下载,就会提示,无法下载
              
    使用ST-Link Utility工具,解除无法下载问题

    此时,连接都无法连接,莫慌。

           

    Target-> Option Bytes...



    把 Read Out Protection 中的 Enabled,改为 Disabled 即可,点击下面“Apply”按键。


    擦除芯片程序

    擦除成功。

    传送门->代码

    五、总结

          好了,就介绍到此,防止别人盗版自己所开发的产品,建议加上此功能。这种加密手段能够在一定程度上增加芯片破解难度,对抄板公司设置门槛。

     

    展开全文
  • 本资源包含最新版的STM32CubeMX中文版教程,非常适合入门CubeMX的初学者,有详细的解释,同时包含L4系列HAL库资料,这种库函数资料较少,可在使用CubeMX时使用。加强理解。
  • 1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 版)》关注官方微信号公众号,获取更多资料:正点原子第五章 SYSTEM 文件夹介绍第三章,我们介绍了如何在 MDK5 下建立 STM32F7 工程。...

    1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子

    69eeaf9d156684cffd0eccb3d02bf745.png

    第五章 SYSTEM 文件夹介绍

    第三章,我们介绍了如何在 MDK5 下建立 STM32F7 工程。在这个新建的工程之中,我们

    用到了一个 SYSTEM 文件夹里面的代码,此文件夹里面的代码由 ALIENTEK 提供,是

    STM32F7xx 系列的底层核心驱动函数,可以用在 STM32F7xx 系列的各个型号上面,方便大家

    快速构建自己的工程。

    SYSTEM 文件夹下包含了 delay、sys、usart 等三个文件夹。分别包含了 delay.c、sys.c、usart.c

    及其头文件。通过这 3 个 c 文件,可以快速的给任何一款 STM32F7 构建最基本的框架。使用

    起来是很方便的。

    本章,我们将向大家介绍这些代码,通过这章的学习,大家将了解到这些代码的由来,也

    希望大家可以灵活使用 SYSTEM 文件夹提供的函数,来快速构建工程,并实际应用到自己的项

    目中去。

    本章包括如下 3 个小结:

    5.1,delay 文件夹代码介绍;

    5.2,sys 文件夹代码介绍;

    5.3,usart 文件夹代码介绍;

    5.1 delay 文件夹代码介绍

    delay 文件夹内包含了 delay.c 和 delay.h 两个文件,这两个文件用来实现系统的延时功能,

    其中包含 7 个函数:

    void delay_osschedlock(void);

    void delay_osschedunlock(void);

    void delay_ostimedly(u32 ticks);

    void SysTick_Handler(void);

    void delay_init(u8 SYSCLK);

    void delay_ms(u16 nms);

    void delay_us(u32 nus);

    前面 4 个函数,仅在支持操作系统(OS)的时候,需要用到,而后面 3 个函数,则不论是

    否支持 OS 都需要用到。

    在介绍这些函数之前,我们先了解一下编程思想:CM4 内核的处理和 CM3 一样,内部都

    包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计到 0 时,将从 RELOAD

    寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就

    永不停息。SysTick 在《STM32F7 中文参考手册》里面基本没有介绍,其详细介绍,请参阅

    《STM32F7 编程手册》第 211 页,4.4 节。我们就是利用 STM32 的内部 SysTick 来实现延时的,

    这样既不占用中断,也不占用系统定时器。

    这里我们将介绍的是 ALIENTEK 提供的最新版本的延时函数,该版本的延时函数支持在任

    意操作系统(OS)下面使用,它可以和操作系统共用 SysTick 定时器。

    这里,我们以 UCOSII 为例,介绍如何实现操作系统和我们的 delay 函数共用 SysTick 定时

    器。首先,我们简单介绍下 UCOSII 的时钟:ucos 运行需要一个系统时钟节拍(类似 “心跳”),

    而这个节拍是固定的(由 OS_TICKS_PER_SEC 宏定义设置),比如要求 5ms 一次(即可设置:

    OS_TICKS_PER_SEC=200),在 STM32 上面,一般是由 SysTick 来提供这个节拍,也就是 SysTick

    要设置为 5ms 中断一次,为 ucos 提供时钟节拍,而且这个时钟一般是不能被打断的(否则就不

    准了)

    因为在 ucos 下 systick 不能再被随意更改,如果我们还想利用 systick 来做 delay_us 或者

    delay_ms 的延时,就必须想点办法了,这里我们利用的是时钟摘取法。以 delay_us 为例,比如

    delay_us(50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 systick 计数次数,这里

    为 50*216(假设系统时钟为 216Mhz,因为我们设置 systick 的频率为系统时钟频率,那么 systick

    每增加 1,就是 1/216us),然后我们就一直统计 systick 的计数变化,直到这个值变化了 50*216,

    一旦检测到变化达到或者超过这个值,就说明延时 50us 时间到了。这样,我们只是抓取 SysTick

    计数器的变化,并不需要修改 SysTick 的任何状态,完全不影响 SysTick 作为 UCOS 时钟节拍

    的功能,这就是实现 delay 和操作系统共用 SysTick 定时器的原理。

    下面我们开始介绍这几个函数。

    5.1.1 操作系统支持宏定义及相关函数

    当需要 delay_ms 和 delay_us 支持操作系统(OS)的时候,我们需要用到 3 个宏定义和 4

    个函数,宏定义及函数代码如下:

    //本例程仅作 UCOSII 和 UCOSIII 的支持,其他 OS,请自行参考着移植

    //支持 UCOSII

    #ifdef OS_CRITICAL_METHOD

    //OS_CRITICAL_METHOD 定义了,说明要支持 UCOSII

    #define delay_osrunning

    OSRunning

    //OS 是否运行标记,0,不运行;1,在运行

    #define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数

    #define delay_osintnesting OSIntNesting

    //中断嵌套级别,即中断嵌套次数

    #endif

    //支持 UCOSIII

    #ifdef CPU_CFG_CRITICAL_METHOD

    //CPU_CFG_CRITICAL_METHOD 定义了,说明要支持 UCOSIII

    #define delay_osrunning

    OSRunning

    //OS 是否运行标记,0,不运行;1,在运行

    #define delay_ostickspersec OSCfg_TickRate_Hz

    //OS 时钟节拍,即每秒调度次数

    #define delay_osintnesting OSIntNestingCtr

    //中断嵌套级别,即中断嵌套次数

    #endif

    //us 级延时时,关闭任务调度(防止打断 us 级延迟)

    void delay_osschedlock(void)

    {

    #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII

    OS_ERR err;

    OSSchedLock(&err);

    //UCOSIII 的方式,禁止调度,防止打断 us 延时

    #else

    //否则 UCOSII

    OSSchedLock();

    //UCOSII 的方式,禁止调度,防止打断 us 延时

    #endif

    }

    //us 级延时时,恢复任务调度

    void delay_osschedunlock(void)

    {

    #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII

    OS_ERR err;

    OSSchedUnlock(&err);

    //UCOSIII 的方式,恢复调度

    #else

    //否则 UCOSII

    OSSchedUnlock();

    //UCOSII 的方式,恢复调度

    #endif

    }

    //调用 OS 自带的延时函数延时

    //ticks:延时的节拍数

    void delay_ostimedly(u32 ticks)

    {

    #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII 时

    OS_ERR err;

    OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);//UCOSIII 延时采用周期模式

    #else

    OSTimeDly(ticks);

    //UCOSII 延时

    #endif

    }

    //systick 中断服务函数,使用 ucos 时用到

    void SysTick_Handler(void)

    {

    if(delay_osrunning==1)

    //OS 开始跑了,才执行正常的调度处理

    {

    OSIntEnter();

    //进入中断

    OSTimeTick();

    //调用 ucos 的时钟服务程序

    OSIntExit();

    //触发任务切换软中断

    }

    }

    以上代码,仅支持 UCOSII 和 UCOSIII,不过,对于其他 OS 的支持,也只需要对以上代

    码进行简单修改即可实现。

    支持 OS 需要用到的三个宏定义(以 UCOSII 为例)即

    #define delay_osrunning

    OSRunning

    //OS 是否运行标记,0,不运行;1,在运行

    #define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数

    #define delay_osintnesting OSIntNesting

    //中断嵌套级别,即中断嵌套次数

    宏定义:delay_osrunning,用于标记 OS 是否正在运行,当 OS 已经开始运行时,该宏定义

    值为 1,当 OS 还未运行时,该宏定义值为 0。

    宏定义:delay_ ostickspersec,用于表示 OS 的时钟节拍,即 OS 每秒钟任务调度次数。

    宏定义:delay_ osintnesting,用于表示 OS 中断嵌套级别,即中断嵌套次数,每进入一个

    中断,该值加 1,每退出一个中断,该值减 1。

    支持 OS 需要用到的 4 个函数,即

    函数:delay_osschedlock,用于 delay_us 延时,作用是禁止 OS 进行调度,以防打断 us 级

    延时,导致延时时间不准。

    函数:delay_osschedunlock,同样用于 delay_us 延时,作用是在延时结束后恢复 OS 的调度,

    继续正常的 OS 任务调度。

    函数:delay_ostimedly,则是调用 OS 自带的延时函数,实现延时。该函数的参数为时钟节

    拍数。

    函数:SysTick_Handler,则是 systick 的中断服务函数,该函数为 OS 提供时钟节拍,同时

    可以引起任务调度。

    以上就是 delay_ms 和 delay_us 支持操作系统时,需要实现的 3 个宏定义和 4 个函数。

    5.1.2 delay_init 函数

    该函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外

    部时钟,如果需要支持操作系统(OS),只需要在 sys.h 里面,设置 SYSTEM_SUPPORT_OS 宏

    的值为 1 即可,然后,该函数会根据 delay_ostickspersec 宏的设置,来配置 SysTick 的中断时间,

    并开启 SysTick 中断。具体代码如下:

    //初始化延迟函数

    //当使用 OS 的时候,此函数会初始化 OS 的时钟节拍

    //SYSTICK 的时钟固定为 HCLK

    void delay_init(u8 SYSCLK)

    {

    #if SYSTEM_SUPPORT_OS

    //如果需要支持 OS.

    u32 reload;

    #endif

    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

    //SysTick 频率为 HCLK

    fac_us=SYSCLK;

    //不论是否使用 OS,fac_us 都需要使用

    #if SYSTEM_SUPPORT_OS

    //如果需要支持 OS.

    reload=SYSCLK;

    //每秒钟的计数次数 单位为 K

    reload*=1000000/delay_ostickspersec; //根据 delay_ostickspersec 设定溢出时间

    //reload 为 24 位寄存器,最大值:16777216,在 180M 下,约合 0.745s 左右

    fac_ms=1000/delay_ostickspersec;

    //代表 OS 可以延时的最少单位

    SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启 SYSTICK 中断

    SysTick->LOAD=reload;

    //每 1/OS_TICKS_PER_SEC 秒中断一次

    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启 SYSTICK

    #else

    #endif

    }

    可以看到,delay_init 函数使用了条件编译,来选择不同的初始化过程,如果不使用 OS 的

    时候,只是设置一下 SysTick 的时钟源以及确定 fac_us 值。而如果使用 OS 的时候,则会进行

    一些不同的配置,这里的条件编译是根据SYSTEM_SUPPORT_OS这个宏来确定的,该宏在sys.h

    里面定义。

    SysTick 是 MDK 定义了的一个结构体(在 core_m4.h 里面),里面包含 CTRL、LOAD、VAL、

    CALIB 等 4 个寄存器,

    SysTick->CTRL 的各位定义如图 5.1.2.1 所示:

    f0b6bb76e4d41fe5aecf653e033bc2aa.png

    图 5.1.2.1 SysTick->CTRL 寄存器各位定义

    SysTick-> LOAD 的定义如图 5.1.2.2 所示:

    d0c75224f71a60e36a3cf01fc6676695.png

    图 5.1.2.2 SysTick->LOAD 寄存器各位定义

    SysTick-> VAL 的定义如图 5.1.2.3 所示:

    f3c66afb326fafd23b5974c0a69eca6e.png

    图 5.1.2.3 SysTick->VAL 寄存器各位定义

    SysTick-> CALIB 不常用,在这里我们也用不到,故不介绍了。

    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);这句代码把 SysTick 的时钟选择为

    内核时钟,这里需要注意的是:SysTick 的时钟源自 HCLK,假设我们外部晶振为 25M,然后

    倍频到 216MHZ,那么 SysTick 的时钟即为 216Mhz,也就是 SysTick 的计数器 VAL 每减 1,就

    代表时间过了 1/216us。所以 fac_us=SYSCLK;这句话就是计算在 SYSCLK 时钟频率下延时 1us

    需要多少个 SysTick 时钟周期。

    在不使用 OS 的时候:fac_us,为 us 延时的基数,也就是延时 1us,Systick 定时器需要走

    过的时钟周期数。 当使用 OS 的时候,fac_us,还是 us 延时的基数,不过这个值不会被写到

    SysTick->LOAD 寄存器来实现延时,而是通过时钟摘取的办法实现的(前面已经介绍了)。而

    fac_ms 则代表 ucos 自带的延时函数所能实现的最小延时时间(如 delay_ostickspersec=200,那

    么 fac_ms 就是 5ms)。

    5.1.3 delay_us 函数

    该函数用来延时指定的 us,其参数 nus 为要延时的微秒数。该函数有使用 OS 和不使用 OS

    两个版本,这里我们首先介绍不使用 OS 的时候,实现函数如下:

    //延时 nus

    //nus 为要延时的 us 数.

    //nus:0~204522252(最大值即 2^32/fac_us@fac_us=21)

    void delay_us(u32 nus)

    {

    u32 ticks;

    u32 told,tnow,tcnt=0;

    u32 reload=SysTick->LOAD;

    //LOAD 的值

    ticks=nus*fac_us;

    //需要的节拍数

    told=SysTick->VAL;

    //刚进入时的计数器值

    while(1)

    {

    tnow=SysTick->VAL;

    if(tnow!=told)

    {

    if(tnow

    else tcnt+=reload-tnow+told;

    told=tnow;

    if(tcnt>=ticks)break;

    //时间超过/等于要延迟的时间,则退出.

    }

    };

    }

    这里就正是利用了我们前面提到的时钟摘取法,ticks 是延时 nus 需要等待的 SysTick 计数

    次数(也就是延时时间),told 用于记录最近一次的 SysTick->VAL 值,然后 tnow 则是当前的

    SysTick->VAL 值,通过他们的对比累加,实现 SysTick 计数次数的统计,统计值存放在 tcnt 里

    面,然后通过对比 tcnt 和 ticks,来判断延时是否到达,从而达到不修改 SysTick 实现 nus 的延

    时。对于使用 OS 的时候,delay_us 的实现函数和不使用 OS 的时候方法类似,都是使用的时钟

    摘取法,只不过使用 delay_osschedlock 和 delay_osschedunlock 两个函数,用于调度上锁和解锁,

    这是为了防止 OS 在 delay_us 的时候打断延时,可能导致的延时不准,所以我们利用这两个函

    数来实现免打断,从而保证延时精度。

    5.1.4 delay_ms 函数

    该函数是用来延时指定的 ms 的,其参数 nms 为要延时的毫秒数。该函数有使用 OS 和不

    使用 OS 两个版本,这里我们分别介绍,首先是不使用 OS 的时候,实现函数如下:

    //延时 nms

    //nms:要延时的 ms 数

    void delay_ms(u16 nms)

    {

    u32 i;

    for(i=0;i< nms;i++) delay_us(1000);

    }

    该函数其实就是多次调用前面所讲的 delay_us 函数,来实现毫秒级延时的。

    再来看看使用 OS 的时候,delay_ms 的实现函数如下:

    //延时 nms

    //nms:要延时的 ms 数

    //nms:0~65535

    void delay_ms(u16 nms)

    {

    if(delay_osrunning&&delay_osintnesting==0)//如果 OS 已经在跑了,且不是在中断里面

    {

    if(nms>=fac_ms)

    //延时的时间大于 OS 的最少时间周期

    {

    delay_ostimedly(nms/fac_ms);

    //OS 延时

    }

    nms%=fac_ms;

    //OS 已经无法提供这么小的延时了,采用普通方式延时

    }

    delay_us((u32)(nms*1000)); //普通方式延时

    }

    该函数中,delay_osrunning 是 OS 正在运行的标志,delay_osintnesting 则是 OS 中断嵌套次

    数,必须 delay_osrunning 为真,且 delay_osintnesting 为 0 的时候,才可以调用 OS 自带的延时

    函数进行延时(可以进行任务调度),delay_ostimedly 函数就是利用 OS 自带的延时函数,实现

    任 务级 延时 的, 其参数 代表 延时 的时 钟节拍 数( 假设 delay_ostickspersec=200 ,那 么

    delay_ostimedly (1),就代表延时 5ms)。

    当 OS 还未运行的时候,我们的 delay_ms 就是直接由 delay_us 实现的,OS 下的 delay_us

    可以实现很长的延时(达到 204 秒)而不溢出!,所以放心的使用 delay_us 来实现 delay_ms,

    不过由于 delay_us 的时候,任务调度被上锁了,所以还是建议不要用 delay_us 来延时很长的时

    间,否则影响整个系统的性能。

    当 OS 运行的时候,我们的 delay_ms 函数将先判断延时时长是否大于等于 1 个 OS 时钟节

    拍(fac_ms),当大于这个值的时候,我们就通过调用 OS 的延时函数来实现(此时任务可以调

    度),不足 1 个时钟节拍的时候,直接调用 delay_us 函数实现(此时任务无法调度)。

    5.1.5 HAL 库延时函数 HAL_Delay 解析

    前面我们讲解了 ALIENTEK 提供的使用 Systick 实现延时相关函数。实际上,HAL 库有提

    供延时函数,只不过它只能实现简单的毫秒级别延时,没有实现 us 级别延时。下面我们列出

    HAL 库实现延时相关的函数。首先是功能配置函数:

    //调用 HAL_SYSTICK_Config 函数配置每隔 1ms 中断一次:文件 stm32f7xx_hal.c 中定义

    __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)

    {

    /*配置 1ms 中断一次*/

    HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0);

    return HAL_OK;

    }

    //HAL 库的 SYSTICK 配置函数:文件 stm32f7xx_hal_context.c 中定义

    uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)

    {

    return SysTick_Config(TicksNumb);

    }

    //内核的 Systick 配置函数,配置每隔 ticks 个 systick 周期中断一次

    //文件 core_cm4.h 中

    __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)

    {

    ...//此处省略函数定义

    }

    上面三个函数,实际上开放给 HAL 调用的主要是 HAL_InitTick 函数,该函数在 HAL 库初

    始化函数 HAL_Init 中会被调用。该函数通过间接调用 SysTick_Config 函数配置 Systick 定时器

    每隔 1ms 中断一次,永不停歇。

    接下来我们来看看延时的逻辑控制代码:

    //Systick 中断服务函数:文件 stm32f7xx_it.c 中

    void SysTick_Handler(void)

    {

    HAL_IncTick();

    }

    //下面代码均在文件 stm32f7xx_hal.c 中

    static __IO uint32_t uwTick; //定义计数全局变量

    __weak void HAL_IncTick(void) //全局变量 uwTick 递增

    {

    uwTick++;

    }

    __weak uint32_t HAL_GetTick(void) //获取全局变量 uwTick 的值

    {

    return uwTick;

    }

    //开放的 HAL 延时函数,延时 Delay 毫秒

    __weak void HAL_Delay(__IO uint32_t Delay)

    {

    uint32_t tickstart = 0;

    tickstart = HAL_GetTick();

    while((HAL_GetTick() - tickstart) < Delay)

    {

    }

    }

    HAL 库实现延时功能非常简单,首先定义了一个 32 位全局变量 uwTick,在 Systick 中断

    服务函数 SysTick_Handler 中通过调用 HAL_IncTick 实现 uwTick 值不断增加,也就是每隔 1ms

    增加 1。而 HAL_Delay 函数在进入函数之后先记录当前 uwTick 的值,然后不断在循环中读取

    uwTick 当前值,进行减运算,得出的就是延时的毫秒数,整个逻辑非常简单也非常清晰。

    但是,HAL库的延时函数有一个局限性,在中断服务函数中使用 HAL_Delay会引起混乱,

    因为它是通过中断方式实现,而 Systick 的中断优先级是最低的,所以在中断中运行 HAL_Delay

    会导致延时出现严重误差。所以一般情况下,推荐大家使用 ALIENTEK 提供的延时函数库。

    5.2 sys 文件夹代码介绍

    sys 文件夹内包含了 sys.c 和 sys.h 两个文件。在 sys.h 里面除了函数申明外主要是定义了一

    些常用数据类型短关键字。sys.c 里面除了定义时钟系统配置函数 Stm32_Clock_Init 外主要是一

    些汇编函数以及 Cache 相关操作函数,对于函数 Stm32_Clock_Init 的讲解请参考本手册 4.3 小

    节 STM32F7 时钟系统章节内容。接下来我们看看 STM32F7 的 Cache 使能函数。

    5.2.1 Cache 使能函数

    STM32F7 自带了指令 Cache(I Cache)和数据 Cache(D Cache),使用 I/D Cache 可以缓存

    指令/数据,提高 CPU 访问指令/数据的速度,从而大大提高 MCU 的性能。不过,MCU 在复位

    后,I/D Cache 默认都是关闭的,为了提高性能,我们需要开启 I/D Cache,在 sys.c 里面,我们

    提供了如下函数:

    //使能 STM32F7 的 L1-Cache,同时开启 D cache 的强制透写

    void Cache_Enable(void)

    {

    SCB_EnableICache(); //使能 I-Cache,函数在 core_cm7.h 里面定义

    SCB_EnableDCache(); //使能 D-Cache,函数在 core_cm7.h 里面定义

    SCB->CACR|=1<<2; //强制 D-Cache 透写,如不开启,实际使用中可能遇到各种问题

    }

    该函数,通过调用 SCB_EnableICache 和 SCB_EnableDCache 这两个函数来使能 I Cache 和

    D Cache。不过,在使能 D Cache 之后,SRAM 里面的数据有可能会被缓存在 Cache 里面,此

    时如果有 DMA 之类的外设访问这个 SRAM 里面的数据,就有可能和 Cache 里面数据不同步,

    导致数据出错,为了防止这种问题,保证数据的一致性,我们设置了 D Cache 的强制透写功能

    (Write Through),这样 CPU 每次操作 Cache 里面的数据,同时也会更新到 SRAM 里面,保证

    D Cache 和 SRAM 里面数据一致。关于 Cache 的详细介绍,请参考《STM32F7 Cache Oveview》

    和《Level 1 cache on STM32F7 Series》(见光盘:8,STM32 参考资料 文件夹)。

    这里 SCB_EnableICache 和 SCB_EnableDCache 这两个函数,是在 core_cm7.h 里面定义的,

    我们直接调用即可,另外,core_cm7.h 里面还提供了以下五个常用函数:

    1,SCB_DisableICache 函数,用于关闭 I Cache。

    2,SCB_DisableDCache 函数,用于关闭 D Cache。

    3,SCB_InvalidateDCache 函数,用于丢弃 D Cache 当前数据,重新从 SRAM 获取数据。

    4,SCB_CleanDCache 函数,用于将 D Cache 数据回写到 SRAM 里面,同步数据。

    5,SCB_CleanInvalidateDCache 函数,用于回写数据到 SRAM,并重新获取 D Cache 数据。

    在 Cache_Enable 函数里面,我们直接开启了 D Cache 的透写模式,这样带来的好处就是可

    以保证D Cache 和SRAM里面数据的一致性,坏处就是会损失一定的性能(每次都要回写数据),

    如果大家想自己控制 D Cache 数据的回写,以获得最佳性能,则可以关闭 D Cache 透写模式,

    并在适当的时候,调用 SCB_CleanDCache、SCB_InvalidateDCache 和 SCB_CleanInvalidateDCache

    等函数,这对程序员的要求非常高,程序员必须清楚什么时候该回写,什么时候该更新 D Cache!

    如果能力不够,还是建议开启 D Cache 的透写,以免引起各种莫名其妙的问题。

    5.3 usart 文件夹介绍

    该文件夹下面有 usart.c 和 usarts.h 两个文件。串口相关知识,我们将在第九章讲解串

    口实验的时候给大家详细讲解。本节我们只给大家讲解比较独立的 printf 函数支持相关的

    知识。

    5.3.1 printf 函数支持

    printf 函数支持的代码在 usart.c 文件的最上方,在我们初始化和使能串口 1 之后,然

    后把这段代码加入到工程,便可以通过 printf 函数向串口 1 发送我们需要的内容,方便开

    发过程中查看代码执行情况以及一些变量值。这段代码如果要修改一般也只是用来改变

    printf 函数针对的串口号,大多情况我们都不需要修改。

    代码内容如下:

    //加入以下代码,支持 printf 函数,而不需要选择 use MicroLIB

    #if 1

    #pragma import(__use_no_semihosting)

    //标准库需要的支持函数

    struct __FILE

    {

    int handle;

    };

    FILE __stdout;

    //定义_sys_exit()以避免使用半主机模式

    _sys_exit(int x)

    {

    x = x;

    }

    //重定义 fputc 函数

    int fputc(int ch, FILE *f)

    {

    while((USART1->SR&0X40)==0);//循环发送,直到发送完毕

    USART1->DR = (u8) ch;

    return ch;

    }

    #endif

    展开全文
  • 1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 版)》关注官方微信号公众号,获取更多资料:正点原子第十三章 PWM 输出实验上一章,我们介绍了STM32F7的通用定时器TIM3,用该定时器的...

    1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子

    cffc7ba04e6f4f29b4491ed7141a2b89.png

    第十三章 PWM 输出实验

    上一章,我们介绍了STM32F7的通用定时器TIM3,用该定时器的中断来控制DS1的闪烁,

    这一章,我们将向大家介绍如何使用 STM32F7 的 TIM3 来产生 PWM 输出。在本章中,我们将

    使用 TIM3 的通道 4 来产生 PWM 来控制 DS0 的亮度。本章分为如下几个部分:

    13.1 PWM 简介

    13.2 硬件设计

    13.3 软件设计

    13.4 下载验证

    13.5 STM32CubeMX 配置定时器 PWM 输出功能

    13.1 PWM 简介

    脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用

    微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽

    度的控制,PWM 原理如图 13.1.1 所示:

    cf60e5b0d02192656d4ea49a555192b7.png

    图 13.1.1 PWM 原理示意图

    图 13.1.1 就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM

    模式,且当 CNT=CCRx 时输出 1。那么就可以得到如上的 PWM

    示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,

    IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。

    改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的

    频率,这就是 PWM 输出的原理。

    STM32F767 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高

    级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多

    达 4 路的 PWM 输出!这里我们仅使用 TIM3 的 CH4 产生一路 PWM 输出。

    要使 STM32F767 的通用定时器 TIMx 产生 PWM 输出,除了上一章介绍的寄存器外,我们

    还会用到 3 个寄存器,来控制 PWM 的。这三个寄存器分别是:捕获/比较模式寄存器

    (TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。

    接下来我们简单介绍一下这三个寄存器。

    首先是捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有 2 个:TIMx _CCMR1和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和 2,而 TIMx_CCMR2 控制 CH3 和 4。以下我们

    将以 TIM3 为例进行介绍。TIM3_CCMR2 寄存器各位描述如图 13.1.2 所示:

    ff3a8b7f3255ca7d53b6a749d782317f.png

    图 13.1.2 TIM3_CCMR2 寄存器各位描述

    该寄存器的有些位在不同模式下,功能不一样,所以在图 13.1.2 中,我们把寄存器分了 2

    层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《STM32F7 中

    文参考手册》第 701 页,23.4.7 节。这里我们需要说明的是模式设置位 OC4M,此部分由 4 位

    组成。总共可以配置成 13 种模式,我们使用的是 PWM 模式,所以这 4 位必须设置为 0110/0111。

    这两种 PWM 模式的区别就是输出电平的极性相反。另外 CC4S 用于设置通道的方向(输入/输

    出)默认设置为 0,就是设置通道作为输出使用。

    接下来,我们介绍 TIM3 的捕获/比较使能寄存器(TIM3_CCER),该寄存器控制着各个输

    入输出通道的开关。该寄存器的各位描述如图 13.1.3 所示:

    637902e79ec462187128424b2005a5b6.png

    图 13.1.3 TIM3_ CCER 寄存器各位描述

    该寄存器比较简单,我们这里只用到了 CC4E 位,该位是输入/捕获 4 输出使能位,要想

    PWM 从 IO 口输出,这个位必须设置为 1,所以我们需要设置该位为 1。该寄存器更详细的介

    绍了,请参考《STM32F7 中文参考手册》第 706 页,23.4.9 这一节。

    最后,我们介绍一下捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有 4 个,对应 4 个

    通道 CH1~4。我们使用的是通道 4,TIM3_CCR4 寄存器的各位描述如图 13.1.4 所示:

    3b1239c7bda28bb3f35b1915efc63e19.png

    图 13.1.4 寄存器 TIM3_CCR4 各位描述

    在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点,

    我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。

    如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配

    置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如图 13.1.5 所示:

    2a47a44531da5cd2c5ceea24b68ef4f4.png

    图 13.1.5 寄存器 TIMx_ BDTR 各位描述

    该寄存器,我们只需要关注第 15 位:MOE 位,要想高级定时器的 PWM 正常输出,则必

    须设置 MOE 位为 1,否则不会有输出。注意:通用定时器不需要配置这个。其他位我们这里就

    不详细介绍了,请参考《STM32F7 中文参考手册》第 639 页,22.4.18 这一节。

    本章,我们使用的是 TIM3 的通道 4,所以我们需要修改 TIM3_CCR4 以实现脉宽控制 DS0

    的亮度。至此,我们把本章要用的几个相关寄存器都介绍完了,下面我们介绍通过 HAL 库来

    配置该功能的步骤。

    首先要提到的是,PWM 实际跟上一章节一样使用的是定时器的功能,所以相关的函数设

    置同样在库函数文件 stm32f7xx_tim.h 和 stm32f7xx_tim.c 文件中。

    1)开启 TIM3 和 GPIO 时钟,配置 PB1 选择复用功能 AF1(TIM3)输出。

    要使用 TIM3,我们必须先开启 TIM14 的时钟,这点相信大家看了这么多代码,应该明白

    了。这里我们还要配置 PB1 为复用(AF1)输出,才可以实现 TIM13_CH4 的 PWM 经过 PB1

    输出。 HAL 库使能 TIM3 时钟和 GPIO 时钟方法是:

    __HAL_RCC_TIM3_CLK_ENABLE();

    //使能定时器 3

    __HAL_RCC_GPIOB_CLK_ENABLE();

    //开启 GPIOB 时钟

    接下来便是要配置 PB1 复用映射为 TIM3 的 PWM 输出引脚。关于 IO 口复用映射,在串口

    通信实验中有详细讲解,主要是通过函数 HAL_GPIO_Init 来实现的:

    GPIO_InitTypeDef GPIO_Initure;

    GPIO_Initure.Pin=GPIO_PIN_1; //PB1

    GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出

    GPIO_Initure.Pull=GPIO_PULLUP; //上拉

    GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速

    GPIO_Initure.Alternate= GPIO_AF2_TIM3; //PB1 复用为 TIM3_CH4

    HAL_GPIO_Init(GPIOB,&GPIO_Initure);

    在 IO 口初始化配置中,我们只需要将成员变量 Mode 配置为复用推挽输出,同时成员变量

    Alternate 配置为 GPIO_AF2_TIM3,即可实现 PB1 映射为定时器 3 通道 4 的 PWM 输出引脚。

    这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看 STM32F7 对应的数据

    手册,比如我们 PWM 实验,我们使用的是定时器 3 的通道 4,对应的引脚 PB1 可以从数据手

    册表中查看:

    5be2e7749f919a9e512af8400b02d527.png

    2)初始化 TIM3,设置 TIM3 的 ARR 和 PSC 等参数。

    根据前面的讲解,初始化定时器的 ARR 和 PSC 等参数是通过函数 HAL_TIM_Base_Init 来

    实现的,但是这里大家要注意,对于我们使用定时器的 PWM 输出功能时,HAL 库为我们提供

    了一个独立的定时器初始化函数 HAL_TIM_PWM_Init,该函数声明为:

    HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);

    该函数实现的功能以及使用方法和 HAL_TIM_Base_Init 都是类似的,作用都是初始化定时

    器 的 ARR 和 PSC 等参 数 。 为什么 HAL 库要提供这个函数而不直接让我们使用

    HAL_TIM_Base_Init 函数呢?

    这 是 因 为 HAL 库 为 定 时 器 的 PWM 输 出 定 义 了 单 独 的 MSP 回 调 函 数

    HAL_TIM_PWM_MspInit,也就是说,当我们调用HAL_TIM_PWM_Init进行PWM初始化之后,

    该函数内部会调用 MSP 回调函数 HAL_TIM_PWM_MspInit。而当我们使用 HAL_TIM_Base_Init

    初始化定时器参数的时候,它内部调用的回调函数为 HAL_TIM_Base_MspInit,这里大家注意

    区分。

    所以大家一定要注意,使用 HAL_TIM_PWM_Init 初始化定时器时,回调函数为:

    HAL_TIM_PWM_MspInit,该函数声明为:

    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);

    一般情况下,上面步骤 1 的时钟使能和 IO 口初始化映射都编写在回调函数内部。

    3)设置 TIM3_CH4 的 PWM 模式,输出比较极性,比较值等参数。

    接下来,我们要设置 TIM3_CH4 为 PWM 模式(默认是冻结的),因为我们的 DS0 是低电

    平亮,而我们希望当 CCR4 的值小的时候,DS0 就暗,CCR4 值大的时候,DS0 就亮,所以我

    们要通过配置 TIM3_CCMR2 的相关位来控制 TIM3_CH4 的模式。

    在 HAL 库中,PWM 通道设置是通过函数 HAL_TIM_PWM_ConfigChannel 来设置的:

    HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,

    TIM_OC_InitTypeDef* sConfig, uint32_t Channel);

    第一个参数 htim 是定时器初始化句柄,也就是 TIM_HandleTypeDef 结构体指针类型,这

    和 HAL_TIM_PWM_Init 函数调用时候参数保存一致即可。

    第二个参数 sConfig 是 TIM_OC_InitTypeDef 结构体指针类型,这也是该函数最重要的参数。

    该参数用来设置 PWM 输出模式,极性,比较值等重要参数。首先我们来看看结构体定义:

    typedef struct

    {

    uint32_t OCMode;

    //PWM 模式

    uint32_t Pulse;

    //捕获比较值

    uint32_t OCPolarity;

    //极性

    uint32_t OCNPolarity;

    uint32_t OCFastMode;

    //快速模式

    uint32_t OCIdleState;

    uint32_t OCNIdleState;

    } TIM_OC_InitTypeDef;

    该结构体成员我们重点关注前三个。成员变量 OCMode 用来设置模式,也就是我们前面讲解的

    7 种模式,这里我们设置为 PWM 模式 1。成员变量 Pulse 用来设置捕获比较值。成员变量

    TIM_OCPolarity 用 来 设 置 输 出 极 性 是 高 还 是 低 。 其 他 的 参 数 TIM_OutputNState ,

    TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是高级定时器才用到的。

    第 三 个 参 数 Channel 用 来 选 择 定 时 器 的 通 道 , 取 值 范 围 为 TIM_CHANNEL_1~

    TIM_CHANNEL_4。这里我们使用的是定时器3的通道4,所以取值为TIM_CHANNEL_4即可。

    例如我们要初始化定时器 3 的通道 4 为 PWM 模式 1,输出极性为低,那么实例代码为:

    TIM_OC_InitTypeDef TIM3_CH4Handler; //定时器 3 通道 4 句柄

    TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1

    TIM3_CH4Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比

    TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低

    HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);

    4)使能 TIM3,使能 TIM3 的 CH4 输出。

    在完成以上设置了之后,我们需要使能 TIM3 并且使能 TIM3_CH4 输出。在 HAL 库中,

    函数 HAL_TIM_PWM_Start 可以用来实现这两个功能,函数声明如下:

    HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);

    该函数第二个入口参数 Channel 是用来设置要使能输出的通道号,这里我们使能的是定时器

    的通道 4,值设置为 TIM_CHANNEL_4 即可。

    对于单独使能定时器的方法,在上一章定时器实验我们已经讲解。实际上,HAL 库也同样

    提供了单独使能定时器的输出通道函数,函数为:

    void TIM_CCxChannelCmd(TIM_TypeDef* TIMx, uint32_t Channel, uint32_t ChannelState);

    5)修改 TIM3_CCR4 来控制占空比。

    最后,在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定

    的,而我们通过修改比较值TIM3_CCR4 则可以控制CH4的输出占空比。继而控制DS0 的亮度。

    HAL 库中并没有提供独立的修改占空比函数,这里我们可以编写这样一个函数如下:

    //设置 TIM3 通道 4 的占空比

    // compare:比较值

    void TIM_SetTIM3Compare4(u32 compare)

    {

    TIM3->CCR4=compare;

    }

    实际上,因为调用函数 HAL_TIM_PWM_ConfigChanne 进行 PWM 配置的时候可以设置比

    较值,所以我们也可以直接使用该函数来达到修改占空比的目的:

    void TIM_SetCompare4(TIM_TypeDef *TIMx,u32 compare)

    {

    TIM3_CH4Handler.Pulse=compare;

    HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);

    }

    这种方法因为要调用 HAL_TIM_PWM_ConfigChannel 函数对各种初始化参数进行重新设

    置,所以大家在使用中一定要注意,例如在实时系统中如果多个线程同时修改初始化结构体相

    关参数,可能导致结果混乱。

    13.2 硬件设计

    本实验用到的硬件资源有:

    1) 指示灯 DS0

    2) 定时器 TIM3

    这两个我们前面都已经介绍了,因为 TIM3_CH4 可以通过 PB1 输出 PWM,而 DS0 就是直

    接节在 PB1 上面的,所以电路上并没有任何变化。

    13.3 软件设计

    打开 PWM 输出实验工程可以看到,我们相比上一节,并没有添加其他任何 HAL 库文件,

    因为 PWM 是使用的定时器资源,所以跟上一讲使用的是同样的 HAL 库文件。同时我们修改了

    timer.c 和 timer.h 的内容,删掉了上一章实验源码,直接把 PWM 功能相关函数和定义放在了这

    两个文件中。

    timer.c 源文件代码如下:

    TIM_HandleTypeDef TIM3_Handler; //定时器 3PWM 句柄

    TIM_OC_InitTypeDef TIM3_CH4Handler; //定时器 3 通道 4 句柄

    //PWM 输出初始化

    //arr:自动重装值 psc:时钟预分频数

    void TIM3_PWM_Init(u16 arr,u16 psc)

    {

    TIM3_Handler.Instance=TIM3;

    //定时器 3

    TIM3_Handler.Init.Prescaler=psc;

    //定时器分频

    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式

    TIM3_Handler.Init.Period=arr; //自动重装载值

    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;

    HAL_TIM_PWM_Init(&TIM3_Handler); //初始化 PWM

    TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1

    TIM3_CH4Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比

    TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低

    HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,

    TIM_CHANNEL_4); //配置 TIM3 通道 4

    HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4);//开启 PWM 通道 4

    }

    //定时器底层驱动,时钟使能,引脚配置

    //此函数会被 HAL_TIM_PWM_Init()调用

    //htim:定时器句柄

    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)

    {

    GPIO_InitTypeDef GPIO_Initure;

    __HAL_RCC_TIM3_CLK_ENABLE();

    //使能定时器 3

    __HAL_RCC_GPIOB_CLK_ENABLE();

    //开启 GPIOB 时钟

    GPIO_Initure.Pin=GPIO_PIN_1; //PB1

    GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出

    GPIO_Initure.Pull=GPIO_PULLUP; //上拉

    GPIO_Initure.Speed=GPIO_SPEED_HIGH;

    //高速

    GPIO_Initure.Alternate= GPIO_AF2_TIM3;

    //PB1 复用为 TIM3_CH4

    HAL_GPIO_Init(GPIOB,&GPIO_Initure);

    }

    //设置 TIM 通道 4 的占空比

    //compare:比较值

    void TIM_SetTIM3Compare4(u32 compare)

    {

    TIM3->CCR4=compare;

    }

    此部分代码包含三个函数,完全实现了前面 13.1 小节讲解的 5 个配置步骤。第一个函数

    TIM3_PWM_Init 实现的是 13.1 小节讲解的步骤 2-4,首先通过调用定时器 HAL 库函数

    HAL_TIM_PWM_Init 初始化 TIM3 并设置 TIM3 的 ARR 和 PSC 等参数,其次通过调用函数

    HAL_TIM_PWM_ConfigChannel 设置 TIM3_CH4 的 PWM 模式以及比较值等参数,最后通过调

    用函数 HAL_TIM_PWM_Start 来使能 TIM3 以及使能 PWM 通道 TIM3_CH4 输出。第二个函数

    HAL_TIM_PWM_MspInit 是 PWM 的 MSP 初始化回调函数,该函数实现的是 13.1 小节步骤 1,

    主要是使能相应时钟以及初始化定时器通道 TIM3_CH4 对应的 IO 口模式,同时设置复用映射

    关系。第三个函数 TIM_SetTIM3Compare 4 是用户自定义的设置比较值函数,这在我们 13.1 小

    节步骤 5 有详细讲解。

    接下来,我们看看 main 函数内容如下:

    int main(void)

    {

    u8 dir=1;

    u16 led0pwmval=0;

    Cache_Enable();

    //打开 L1-Cache

    HAL_Init();

    //初始化 HAL 库

    Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz

    delay_init(216); //延时初始化

    uart_init(115200);

    //串口初始化

    LED_Init(); //初始化 LED

    TIM3_PWM_Init(500-1,108-1); //108M/108=1M 的计数频率,自动重装载为 500,

    //那么 PWM 频率为 1M/500=2kHZ

    while(1)

    {

    delay_ms(10);

    if(dir)led0pwmval++;

    //dir==1 led0pwmval 递增

    else led0pwmval--;

    //dir==0 led0pwmval 递减

    if(led0pwmval>300)dir=0;

    //led0pwmval 到达 300 后,方向为递减

    if(led0pwmval==0)dir=1;

    //led0pwmval 递减到 0 后,方向改为递增

    TIM_SetTIM3Compare4(led0pwmval);//修改比较值,修改占空比

    }

    }

    这里,我们从死循环函数可以看出,我们控制 LED0_PWM_VAL 的值从 0 变到 300,然后

    又从 300 变到 0,如此循环,因此 DS0 的亮度也会跟着从暗变到亮,然后又从亮变到暗。至于

    这里的值,我们为什么取 300,是因为 PWM 的输出占空比达到这个值的时候,我们的 LED 亮

    度变化就不大了(虽然最大值可以设置到 499),因此设计过大的值在这里是没必要的。至此,

    我们的软件设计就完成了。

    13.4 下载验证

    在完成软件设计之后,将我们将编译好的文件下载到阿波罗 STM32 开发板上,观看其运

    行结果是否与我们编写的一致。如果没有错误,我们将看 DS0 不停的由暗变到亮,然后又从亮

    变到暗。每个过程持续时间大概为 3 秒钟左右。

    实际运行结果如下图 13.4.1 所示:

    40c51ae569cb6817f81cce5da5a5bbd5.png

    图 13.4.1 PWM 控制 DS0 亮度

    13.5 STM32CubeMX 配置定时器 PWM 输出功能

    使用 STM32CubeMX 配置 PWM 输出的配置步骤和配置定时器中断的配置步骤非常接近,

    步骤如下:

    ① 在 Pinout->TIM3 配置项中,配置 Channel4 的值为 PWM generation CH4,然后 Clock

    Source 为 Internal Clock。操作过程如下图 13.5.1 所示:

    cfb97d46aff172c33f00beb77d7b04d6.png

    图 13.5.1 TIM3 配置

    ② 进入 Configuration->TIM3 配置页,在弹出的界面中点击 Parameter Settings 选项卡,

    Counter Settings 配置栏下面的四个选项就是用来配置定时器的预分频系数,自动装载

    值,计数模式以及时钟分频因子。在界面的 PWM Generation Channel4 配置栏配置 PWM

    模式,比较值,极性等参数,操作方法如下图 13.5.2 所示:

    478f72d4660094c873884a90b5f503f5.png

    图 12.5.2 TIM3 参数设置界面

    本章 PWM 输出实验,我们并没有使用到中断,所以我们不需要使能中断和配置 NVIC。

    经过上面的配置就可以生成工程源码,大家可以和本实验工程对比参考学习。

    展开全文
  • 1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 版)》关注官方微信号公众号,获取更多资料:正点原子第二十九章 DMA 实验本章我们将向大家介绍 STM32F767 的 DMA。在本章中,我们将...

    1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子

    1ba1d88df26bac533c995baeb92bccef.png

    第二十九章 DMA 实验

    本章我们将向大家介绍 STM32F767 的 DMA。在本章中,我们将利用 STM32F767 的 DMA

    来实现串口数据传送,并在 LCD 模块上显示当前的传送进度。本章分为如下几个部分:

    29.1 STM32F767 DMA 简介

    29.2 硬件设计

    29.3 软件设计

    29.4 下载验证

    29.1 STM32F767 DMA 简介

    DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接

    控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备

    开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

    STM32F767 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),

    每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达

    8 个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

    STM32F767 的 DMA 有以下一些特性:

    ● 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问

    ● 仅支持 32 位访问的 AHB 从编程接口

    ● 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)

    ● 每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直

    接模式。

    ● 通过硬件可以将每个数据流配置为:

    1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道

    2,支持在存储器方双缓冲的双缓冲区通道

    ● 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)

    ● DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在

    软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)

    ● 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)

    ● 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动

    DMA 请求

    ● 要传输的数据项的数目可以由 DMA 控制器或外设管理:

    1,DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程

    2,外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬

    件发出传输结束的信号

    ● 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA

    自动封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用。

    ● 对源和目标的增量或非增量寻址

    ● 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等

    于外设 FIFO 大小的一半

    ● 每个数据流都支持循环缓冲区管理

    ● 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误

    直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

    STM32F767 有两个 DMA 控制器,DMA1 和 DMA2,本章,我们仅针对 DMA2 进行介绍。

    STM32F767 的 DMA 控制器框图如图 29.1.1 所示:

    de9280077ccc47abf0af839055f761b6.png

    图 29.1.1 DMA 控制器框图

    DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来

    启动 AHB 事务。它可以执行下列事务:

    1,外设到存储器的传输

    1, 存储器到外设的传输

    3,存储器到存储器的传输

    这里特别注意一下,存储器到存储器需要外设接口可以访问存储器,而仅 DMA2 的外设接

    口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输,DMA1 不支持。

    图 29.1.1 中数据流的多通道选择,是通过 DMA_SxCR 寄存器控制的,如图 29.1.2 所示:

    c1b130b47df7b1033a8c44df3db293dd.png

    图 29.1.2 DMA 数据流通道选择

    从上图可以看出,DMA_SxCR 控制数据流到底使用哪一个通道,每个数据流有 8 个通道可

    供选择,每次只能选择其中一个通道进行 DMA 传输。接下来,我们看看 DMA2 的各数据流通

    道映射表,如表 29.1.1 所示:

    0fd6f423d743b02991ecc30009138ec4.png

    表 29.1.1 DMA2 各数据流通道映射表

    上表就列出了 DMA2 所有可能的选择情况,来总共 64 种组合,比如本章我们要实现串口 1

    的 DMA 发送,即 USART1_TX,就必须选择 DMA2 的数据流 7,通道 4,来进行 DMA 传输。这里注

    意一下,有的外设(比如 USART1_RX)可能有多个通道可以选择,大家随意选择一个就可以了。

    接下来,我们介绍一下 DMA 设置相关的几个寄存器。

    第一个是 DMA 中断状态寄存器,该寄存器总共有 2 个:DMA_LISR 和 DMA_HISR,每个寄存器

    管理 4 数据流(总共 8 个),DMA_LISR 寄存器用于管理数据流 0~3,而 DMA_HISR 用于管理数据

    流 4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。

    这里,我们仅以 DMA_LISR 寄存器为例进行介绍,DMA_LISR 各位描述如图 29.1.3 所示:

    0253d0093e855cd8d0ace3f383c1281b.png

    图 29.1.3 DMA_LISR 寄存器各位描述

    如果开启了 DMA_LISR 中这些位对应的中断,则在达到条件后就会跳到中断服务函数里面去,

    即使没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx

    位,即数据流 x 的 DMA 传输完成与否标志。注意此寄存器为只读寄存器,所以在这些位被置位

    之后,只能通过其他的操作来清除。DMA_HISR 寄存器各位描述通 DMA_LISR 寄存器各位描述完

    全一样,只是对应数据流 4~7,这里我们就不列出来了。

    第二个是 DMA 中断标志清除寄存器, 该寄存器同样有 2 个:DMA_LIFCR 和 DMA_HIFCR,同样

    是每个寄存器控制 4 个数据流,DMA_LIFCR 寄存器用于管理数据流 0~3,而 DMA_ HIFCR 用于管

    理数据流 4~7。这两个寄存器各位描述都完全一模一样,只是管理的数据流不一样。

    这里,我们仅以 DMA_LIFCR 寄存器为例进行介绍,DMA_LIFCR 各位描述如图 29.1.4 所示:

    dd162e409b4fa7a96f4f63e4dbb5f91e.png

    图 29.1.4 DMA_LIFCR 寄存器各位描述

    DMA_LIFCR 的各位就是用来清除 DMA_LISR 的对应位的,通过写 1 清除。在 DMA_LISR 被置

    位后,我们必须通过向该位寄存器对应的位写入 1 来清除。DMA_HIFCR 的使用同 DMA_LIFCR 类

    似,这里就不做介绍了。

    第三个是 DMA 数据流 x 配置寄存器(DMA_SxCR)(x=0~7,下同)。该寄存器的我们在这里就

    不贴出来了,见《STM32F7 中文参考手册》第 229 页 8.5.5 一节。该寄存器控制着 DMA 的很多

    相关信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、

    使能等都是通过该寄存器来设置的。所以 DMA_ SxCR 是 DMA 传输的核心控制寄存器。

    第四个是 DMA 数据流 x 数据项数寄存器(DMA_SxNDTR)。这个寄存器控制 DMA 数据流 x 的每

    次传输所要传输的数据量。其设置范围为 0~65535。并且该寄存器的值会随着传输的进行而减

    少,当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个

    寄存器的值来知道当前 DMA 传输的进度。特别注意,这里是数据项数目,而不是指的字节数。

    比如设置数据位宽为 16 位,那么传输一次(一个项)就是 2 个字节。

    第五个是 DMA 数据流 x 的外设地址寄存器(DMA_SxPAR)。该寄存器用来存储 STM32F767 外

    设的地址,比如我们使用串口 1,那么该寄存器必须写入 0x40011028(其实就是&USART1_TDR)。

    如果使用其他外设,就修改成相应外设的地址就行了。

    最后一个是 DMA 数据流 x 的存储器地址寄存器,由于 STM32F767 的 DMA 支持双缓存,所以

    存储器地址寄存器有两个:DMA_SxM0AR 和 DMA_SxM1AR,其中 DMA_SxM1AR 仅在双缓冲模式下,

    才有效。本章我们没用到双缓冲模式,所以存储器地址寄存器就是:DMA_SxM0AR,该寄存器和

    DMA_CPARx 差不多,但是是用来放存储器的地址的。比如我们使用 SendBuf[7800]数组来做存储

    器,那么我们在 DMA_SxM0AR 中写入&SendBuff 就可以了。

    DMA 相关寄存器就为大家介绍到这里,关于这些寄存器的详细描述,请参考《STM32F7xx

    中文参考手册》第 8.5 节。本章我们要用到串口 1 的发送,属于 DMA2 的数据流 7,通道 4,接

    下来我们就介绍下使用 HAL 库的配置步骤和方法。首先这里我们需要指出的是,DMA 相关的库

    函数支持在文件 stm32f7xx_hal_dma.c/stm32f7xx_hal_dma_ex.c 以及对应的头文件中,同时因

    为我们是用串口的 DMA 功能,所以还要加入串口相关的文件 stm32f7xx_hal_uart.c。具体步骤

    如下:

    1)使能 DMA2 时钟。

    DMA 的时钟使能是通过 AHB1ENR 寄存器来控制的,这里我们要先使能时钟,才可以配置 DMA

    相关寄存器。HAL 库方法为:

    __HAL_RCC_DMA2_CLK_ENABLE();//DMA2 时钟使能

    __HAL_RCC_DMA1_CLK_ENABLE();//DMA1 时钟使能

    2) 初始化 DMA2 数据流 7,包括配置通道,外设地址,存储器地址,传输数据量等。

    DMA 的某个数据流各种配置参数初始化是通过 HAL_DMA_Init 函数实现的,该函数声明为:

    HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);

    该函数只有一个 DMA_HandleTypeDef 结构体指针类型入口参数,结构体定义为:

    typedef struct __DMA_HandleTypeDef

    {

    DMA_Stream_TypeDef *Instance;

    DMA_InitTypeDef Init;

    HAL_LockTypeDef Lock;

    __IO HAL_DMA_StateTypeDef State;

    void *Parent;

    void (* XferCpltCallback)(

    struct __DMA_HandleTypeDef * hdma);

    void (* XferHalfCpltCallback)(

    struct __DMA_HandleTypeDef * hdma);

    void (* XferM1CpltCallback)(

    struct __DMA_HandleTypeDef * hdma);

    void (* XferM1HalfCpltCallback)(

    struct __DMA_HandleTypeDef * hdma);

    void (* XferErrorCallback)(

    struct __DMA_HandleTypeDef * hdma);

    void (* XferAbortCallback)(

    struct __DMA_HandleTypeDef * hdma);

    __IO uint32_t ErrorCode;

    uint32_t StreamBaseAddress;

    uint32_t StreamIndex;

    }DMA_HandleTypeDef;

    成员变量 Instance 是用来设置寄存器基地址,例如要设置为 DMA2 的数据流 7,那么取值

    为 DMA2_Stream7。

    成员变量 Parent 是 HAL 库处理中间变量,用来指向 DMA 通道外设句柄。

    成员变量 XferCpltCallback(传输完成回调函数), XferHalfCpltCallback(半传输完成

    回调函数), XferM1CpltCallback(Memory1 传输完成回调函数),XferM1HalfCpltCallback

    ( Memory1 半 传 输 完 成 回 调 函 数 ), XferErrorCallback ( 传 输 错 误 回 调 函 数 ) 和

    XferAbortCallback(传输中断回调函数)是六个函数指针,用来指向回调函数入口地址。

    成员变量 StreamBaseAddress 和 StreamIndex 是数据流基地址和索引号,这个是 HAL 库处

    理的时候会自动计算,用户无需设置。

    其他成员变量 HAL 库处理过程状态标识变量,这里就不做过多讲解。接下来我们着重看看

    成员变量 Init,它是 DMA_InitTypeDef 结构体类型,该结构体定义为:

    typedef struct

    {

    uint32_t Channel; //通道,例如:DMA_CHANNEL_4

    uint32_t Direction;//传输方向,例如存储器到外设 DMA_MEMORY_TO_PERIPH

    uint32_t PeriphInc;//外设(非)增量模式,非增量模式 DMA_PINC_DISABLE

    uint32_t MemInc;//存储器(非)增量模式,增量模式 DMA_MINC_ENABLE

    uint32_t PeriphDataAlignment; //外设数据大小:8/16/32 位。

    uint32_t MemDataAlignment; //存储器数据大小:8/16/32 位。

    uint32_t Mode;//模式:外设流控模式/循环模式/普通模式

    uint32_t Priority; //DMA 优先级:低/中/高/非常高

    uint32_t FIFOMode;//FIFO 模式开启或者禁止

    uint32_t FIFOThreshold; //FIFO 阈值选择:

    uint32_t MemBurst; //存储器突发模式:单次/4 个节拍/8 个节拍/16 个节拍

    uint32_t PeriphBurst; //外设突发模式:单次/4 个节拍/8 个节拍/16 个节拍

    }DMA_InitTypeDef;

    该结构体成员变量非常多,但是每个成员变量配置的基本都是 DMA_SxCR 寄存器和

    DMA_SxFCR 寄存器的相应位。我们把结构体各个成员变量的含义都通过注释的方式列出来了。

    例如本实验我们要用到 DMA2_Stream7 的 DMA_CHANNEL_4,把内存中数组的值发送到串口外设发

    送寄存器 DR,所以方向为存储器到外设 DMA_MEMORY_TO_PERIPH,一个一个字节发送,需要数字

    索引自动增加,所以是存储器增量模式 DMA_MINC_ENABLE,存储器和外设的字宽都是字节 8 位。

    具体配置如下:

    DMA_HandleTypeDef UART1TxDMA_Handler;

    //DMA 句柄

    UART1TxDMA_Handler.Instance= DMA2_Stream7; //数据流选择

    UART1TxDMA_Handler.Init.Channel=DMA_CHANNEL_4; //通道选择

    UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设

    UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式

    UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式

    UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外设:8 位

    UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE; //存储器:8 位

    UART1TxDMA_Handler.Init.Mode=DMA_NORMAL; //普通模式

    UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级

    UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;

    UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;

    UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器突发单次传输

    UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输

    这里大家要注意,HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用

    一个宏定义标识符,来连接 DMA 和外设句柄。例如要使用串口 DMA 发送,所以方式为:

    __HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);

    其中 UART1_Handler 是串口初始化句柄,我们在 usart.c 中定义过了。UART1TxDMA_Handler

    是 DMA 初始化句柄。hdmatx 是外设句柄结构体的成员变量,在这里实际就是 UART1_Handler 的

    成员变量。在 HAL 库中,任何一个可以使用 DMA 的外设,它的初始化结构体句柄都会有一个

    DMA_HandleTypeDef 指针类型的成员变量,是 HAL 库用来做相关指向的。Hdmatx 就是

    DMA_HandleTypeDef 结构体指针类型。

    这 句 话 的 含 义 就 是 把 UART1_Handler 句 柄 的 成 员 变 量 hdmatx 和 DMA 句 柄

    UART1TxDMA_Handler 连接起来,是纯软件处理,没有任何硬件操作。

    这里我们就点到为止,如果大家要详细了解 HAL 库指向关系,请查看本实验宏定义标识符

    __HAL_LINKDMA 的定义和调用方法,就会很清楚了。

    3)使能串口 1(DMA2_Stream7)的 DMA 发送,启动传输

    串口 1 的 DMA 发送实际是串口控制寄存器 CR3 的位 7 来控制的,在 HAL 库中操作该寄存器

    来使能串口 DMA 发送的函数为 HAL_UART_Transmit_DMA,该函数声明如下:

    HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart,

    uint8_t *pData, uint16_t Size);

    这里大家需要注意,调用该函数后会开启相应的 DMA 中断,对于本章实验,我们是通过查

    询的方法获取数据传输状态,所以并没有做中断相关处理,也没有编写中断服务函数。

    HAL 库还提供了对串口的 DMA 发送的停止,暂停,继续等操作函数:

    HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart); //停止

    HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart); //暂停

    HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart);//恢复

    这些函数使用方法这里我们就不累赘了。

    4)查询 DMA 传输状态

    在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的方法是:

    __HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7);

    获取当前传输剩余数据量:

    __HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);

    同样,我们也可以设置对应的 DMA 数据流传输的数据量大小,函数为:

    __HAL_DMA_SET_COUNTER(&UART1TxDMA_Handler,1000);

    DMA 相关的库函数我们就讲解到这里,大家可以查看固件库中文手册详细了解。

    5)DMA 中断使用方法

    DMA 中断对于每个流都有一个中断服务函数,比如 DMA2_Stream7 的中断服务函数为

    DMA2_Stream7_IRQHandler。同样,HAL 库也提供了一个通用的 DMA 中断处理函数

    HAL_DMA_IRQHandler,在该函数内部,会对 DMA 传输状态进行分析,然后调用相应的中断

    处理回调函数:

    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数

    void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);/发送一半回调函数

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数

    void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收一半回调函数

    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//传输出错回调函数

    29.2 硬件设计

    所以本章用到的硬件资源有:

    1) 指示灯 DS0

    2) KEY0 按键

    3) 串口

    4) LCD 模块

    5) DMA

    本章我们将利用外部按键 KEY0 来控制 DMA 的传送,每按一次 KEY0,DMA 就传送一次数据到

    USART1,然后在 LCD 模块上显示进度等信息。DS0 还是用来做为程序运行的指示灯。

    本章实验需要注意 P4 口的 RXD 和 TXD 是否和 PA9 和 PA10 连接上,如果没有,请先连接。

    29.3 软件设计

    打开本章的实验工程可以看到,我们在 HALLIB 分组下面增加了 DMA 支持文件

    stm32f7xx_dma.c,同时引入了 stm32f7xx_hal_dma.h 头文件支持。在 HARDWARE 分组下面我

    们新增了 dma.c 以及对应头文件 dma.h 用来存放 dma 相关的函数和定义。

    打开 dma.c 文件,代码如下:

    DMA_HandleTypeDef UART1TxDMA_Handler; //DMA 句柄

    //DMAx 的各通道配置

    //这里的传输形式是固定的,这点要根据不同的情况来修改

    //从存储器->外设模式/8 位数据宽度/存储器增量模式

    //DMA_Streamx:DMA 数据流,DMA1_Stream0~7/DMA2_Stream0~7

    //chx:DMA 通道选择,@ref DMA_channel DMA_CHANNEL_0~DMA_CHANNEL_7

    void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx)

    {

    if((u32)DMA_Streamx>(u32)DMA2)//得到当前 stream 是属于 DMA2 还是 DMA1

    {

    __HAL_RCC_DMA2_CLK_ENABLE();//DMA2 时钟使能

    }else

    {

    __HAL_RCC_DMA1_CLK_ENABLE();//DMA1 时钟使能

    }

    __HAL_LINKDMA(&UART1_Handler,hdmatx,UART1TxDMA_Handler);

    //将 DMA 与 USART1 联系起来(发送 DMA)

    //Tx DMA 配置

    UART1TxDMA_Handler.Instance=DMA_Streamx;

    //数据流选择

    UART1TxDMA_Handler.Init.Channel=chx;

    //通道选择

    UART1TxDMA_Handler.Init.Direction=DMA_MEMORY_TO_PERIPH; //存储器到外设

    UART1TxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式

    UART1TxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式

    UART1TxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;//外设 8 位

    UART1TxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE; //存储器:8 位

    UART1TxDMA_Handler.Init.Mode=DMA_NORMAL;

    //外设普通模式

    UART1TxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级

    UART1TxDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE;

    UART1TxDMA_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_FULL;

    UART1TxDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器突发单次传输

    UART1TxDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输

    HAL_DMA_DeInit(&UART1TxDMA_Handler); //把 DMA 寄存器设置为缺省值

    HAL_DMA_Init(&UART1TxDMA_Handler); //初始化 DMA

    }

    该部分代码仅仅 1 个函数,MYDMA_Config 函数,基本上就是按照我们上面 29.1 小节介绍

    的步骤 1 和步骤 2 来使能 DMA 时钟和初始化 DMA 的,该函数是一个通用的 DMA 配置函数,

    DMA1/DMA2 的所有通道,都可以利用该函数配置,不过有些固定参数可能要适当修改(比如位

    宽,传输方向等)。该函数在外部只能修改 DMA 数据流编号和通道号,更多的其他设置只能在该

    函数内部修改。对照前面的配置步骤的详细讲解来分析这部分代码即可。

    dma.h 头文件内容比较简单,主要是函数申明,这里我们不细说。

    接下来我们看看那 main 函数如下:

    #define SEND_BUF_SIZE 7800//发送数据长度最好等于 sizeof(TEXT_TO_SEND)+2 的整数倍

    u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区

    const u8 TEXT_TO_SEND[]={"ALIENTEK Apollo STM32F4 DMA 串口实验"};

    int main(void)

    {

    u16 i;

    u8 t=0;

    u8 j,mask=0;

    float pro=0; //进度

    Cache_Enable(); //打开 L1-Cache

    HAL_Init();

    //初始化 HAL 库

    Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz

    delay_init(216); //延时初始化

    uart_init(115200);

    //串口初始化

    LED_Init(); //初始化 LED

    KEY_Init(); //初始化按键

    SDRAM_Init(); //初始化 SDRAM

    LCD_Init(); //LCD 初始化

    MYDMA_Config(DMA2_Stream7,DMA_CHANNEL_4);//初始化 DMA

    POINT_COLOR=RED;

    LCD_ShowString(30,50,200,16,16,"Apollo STM32F4/F7");

    LCD_ShowString(30,70,200,16,16,"DMA TEST");

    LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");

    LCD_ShowString(30,110,200,16,16,"2016/1/24");

    LCD_ShowString(30,130,200,16,16,"KEY0:Start");

    POINT_COLOR=BLUE;//设置字体为蓝色

    //显示提示信息

    j=sizeof(TEXT_TO_SEND);

    for(i=0;i

    {

    if(t>=j)//加入换行符

    {

    if(mask)

    {

    SendBuff[i]=0x0a;

    t=0;

    }else

    {

    SendBuff[i]=0x0d;

    mask++;

    }

    }else//复制 TEXT_TO_SEND 语句

    {

    mask=0;

    SendBuff[i]=TEXT_TO_SEND[t];

    t++;

    }

    }

    POINT_COLOR=BLUE;//设置字体为蓝色

    i=0;

    while(1)

    {

    t=KEY_Scan(0);

    if(t==KEY0_PRES) //KEY0 按下

    {

    printf("DMA DATA:");

    LCD_ShowString(30,150,200,16,16,"Start Transimit....");

    LCD_ShowString(30,170,200,16,16," %") ; //显示百分号

    HAL_UART_Transmit_DMA(&UART1_Handler,SendBuff,

    SEND_BUF_SIZE);//开启 DMA 传输

    while(1)

    {

    if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7))

    //等待 DMA2_Steam7 传输完成

    {

    __HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler,DMA_FLAG_TCIF3_7);

    //清除 DMA2_Steam7 传输完成标志

    HAL_UART_DMAStop(&UART1_Handler)//传输完成以后关闭串口 DMA

    break;

    }

    pro=__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler);

    //得到当前还剩余多少个数据

    pro=1-pro/SEND_BUF_SIZE; //得到百分比

    pro*=100;

    //扩大 100 倍

    LCD_ShowNum(30,170,pro,3,16);

    }

    LCD_ShowNum(30,170,100,3,16);//显示 100%

    LCD_ShowString(30,150,200,16,16,"Transimit Finished!");//提示完成

    }

    i++;

    delay_ms(10);

    if(i==20)

    {

    LED0_Toggle;//提示系统正在运行

    i=0;

    }

    }

    }

    main 函数的流程大致是:先初始化内存 SendBuff 的值,然后通过 KEY0 开启串口 DMA 发送,

    在发送过程中,通过__HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler)获取当前还剩余的数据量

    来计算传输百分比,最后在传输结束之后清除相应标志位,提示已经传输完成。

    至此,DMA 串口传输的软件设计就完成了。

    29.4 下载验证

    在代码编译成功之后,我们通过串口下载代码到 ALIENTEK 阿波罗 STM32 开发板上,可

    以看到 LCD 显示如图 29.4.1 所示:

    c8f66c0d484e0c97798a222a678c5a3c.png

    图 29.4.1 DMA 实验测试图

    伴随 DS0 的不停闪烁,提示程序在运行。我们打开串口调试助手,然后按 KEY0,可以看

    到串口显示如图 29.4.2 所示的内容:

    df47819bad5d9e779df86d39bff7241d.png

    图 29.4.2 串口收到的数据内容

    可以看到串口收到了阿波罗 STM32F767 开发板发送过来的数据,同时可以看到 TFTLCD 上

    显示了进度等信息,如图 29.4.3 所示:

    11e71c5eaece6699e7dc8d9c1f06501d.png

    29.4.3 DMA 串口数据传输中

    至此,我们整个 DMA 实验就结束了,希望大家通过本章的学习,掌握 STM32F767 的 DMA

    使用。DMA 是个非常好的功能,它不但能减轻 CPU 负担,还能提高数据传输速度,合理的应

    用 DMA,往往能让你的程序设计变得简单。

    展开全文
  • 说明:本文原创作者『strongerHuang』首发于微信公众号『嵌入式专栏』,同时也更新在我的个人网站:EmbeddedDevelop标签:STM32STM32CubeMX、 LL库、 HAL库一、写在前面上一篇文章让你入门STM32CubeMX,你可能...
  • 正点原子:STM32F1开发指南、STM32F1中文参考手册 Z小璇博客:【STM32HAL库 STM32CubeMX教程九—ADC 1. ADC的转换模式 1。单次转换模式:ADC只执行一次转换; 2。连续转换模式:转换结束之后马上开始新的转换; ...
  • HAL库-STM32F4 UART-IT

    千次阅读 2016-05-08 16:53:36
    参考资料:ST-《STM32F4xx中文参考手册》、野火-《零死角玩转STM-F429》 主要数据类型: UART_HandleTypeDef UartHandle; 主要HAL函数: 初始化函数: HAL_StatusTypeDef HAL_UART_Init ( UART_HandleTypeDef * ...
  • 编者按:路漫漫其修远兮,吾将上下而求索。这句从小学在“日积月累”模块背下来的话,是对屈原最深刻的印象了。...Z小旋的博客:【STM32HAL库 STM32CubeMX教程六----定时器中断 Z小璇的博客:【ST.
  • 具体可参考STM32F4xx中文参考手册。 对于片内FLASH,主要有擦除编写和读取操作。擦除一般以扇区为单位,在编写之前,将4k的扇区擦除。读取就是从某个地址addr,读取一个字,可用如下语句实现,data=*(vu32*)addr。 ...
  • HAL库-STM32F4 外部中断-延时

    万次阅读 2016-04-30 21:47:00
    注:资料来源:野火《零死角玩转STM32-F429》、ST-《STM32F4xx中文参考手册》、ST-《Cortex™-M4内核编程手册》 开始: 1.嵌套向量中断寄存器 (NVIC): 嵌套向量中断控制器 (NVIC) 和处理器内核接口紧密配合,可以...
  • 关于GPIO寄存器相关知识,可以参阅stm32F4xx中文参考手册,里面对于硬件底层以及寄存器有详细的介绍,本文不再详细展开。在使用HAL库的时候,即使不了解底层硬件和寄存器知识,同样可以完成项目的开发。此处,只需要...
  • 造成中文手册误解的原因:有的把stream翻译成中文“通道”,这里我还是直接使用英文比较好;因为表中还有一个单词"channel",这个单词在中文里更容易被翻译成“通道”,但事实上这里只涉及stream。 当然,如果你...
  • 【朱老师课程总结 侵删】 第一部分、章节目录 ...3.1.8.STM32的标准HAL(cubeMX)是怎么回事 3.1.9.本课程使用的开发板介绍 第二部分、章节介绍 3.1.1.STM32的来历背景1 本节对STM3...
  • HAL库版本:STM32Cube_FW_F1_V1.8.0 本文内容: STM32F1x HAL库硬件 I2C 通信 MPU9250 使用 DMP 输出姿态角:Roll,Pitch,Yaw MPU9250 中 DMP 的移植 附件: MDK5 STM32F1 示例工程 MPU9250 中文手册 MPU9250...
  • STM32L0系列-dataSheet.zip

    2020-07-18 09:45:45
    STM32L0系列中文参考手册与STM32L051C8T6英文手册,对应博客“基于STM32HAL库ADC+DMA模式,高精度采集电池电量与芯片内部温度方法 (48脚 使用内部参考电压方案)”
  • 1)实验平台:alientek NANO STM32F411 V1开发板2)摘自《正点原子STM32F4 开发指南(HAL 版》关注官方微信号公众号,获取更多资料:正点原子第二十八章 DHT11 数字温湿度传感器实验上一章,我们介绍了数字温度传感器...
  • HAL库的配套指导文档,特别是中文的使用手册文档欠缺得很厉害,除了野火在2017年上半年出了一套STM32F4的HAL库教程,就几乎没有任何由ST官方协助完成的教程了。 2.HAL库全称是Hardware Abstracti...
  • 第一部分、章节目录 3.1.1.STM32的来历背景1 3.1.2.STM32的来历背景2 3.1.3.STM32的来历背景3 3.1.4.STM32简单中文手册带读1 ...3.1.8.STM32的标准HAL(cubeMX)是怎么回事 3.1.9.本课程使用的开...
  • 本文章根据stm32f4xx中文参考手册整理,可供学习其他arm内核单片机/stm32系列单片机参考 如果对其中的内容有疑问,可以参考RCC、定时器、中断相关部分的解析 以下内容使用SPL库(标准库)作为代码示例,HAL库是更...
  • 初探STM32F4(1)–GPIO

    2021-01-20 10:58:37
    通用I/OGPIO概述GPIO硬件架构...结合《STM32F4xx中文参考手册》,从硬件和寄存器的角度剖析GPIO 结合例程,从软件角度剖析GPIO如何配置,同时剖析嵌入式代码规范 阅读完本文,要能回答以下问题: 简述GPIO口的硬件架
  • STM32驱动AD7190

    千次阅读 2018-12-09 12:10:22
    这篇介绍的时高精度、速度AD芯片AD7190的驱动,针对STM32,并且使用的时HAL库,改成其他库也很容易,只需要把相关通讯部分改了就行。我在上网找到的中文数据手册:...
  • 阿波罗stm32f767开发板

    2018-07-05 15:09:49
    -1,ALIENTEK阿波罗STM32F767开发板入门资料 | |--MDK5.21A安装手册.pdf---------------------------------安装编译器指导文档 | |--STLINK调试补充教程.pdf-------------------------------ST LINK安装使用教程...
  •   在STM32的定时器中,TIMx_PSC、 TIM_ARR两个寄存器加上捕捉比较模块中TIMX_CCR寄存器,它们都可以动态修改。不过他们的修改和生效可能不在同...(中文手册P393)   实际上,STM32定时器中,CNT与ARR或CCR的比...
  • 这里我说一下Cube的弊端,它使用的HAL和LL,而我一开始是从源享科技的刘凯老师那里学的stm32,用的是标准固件。如果你会用标准固件,其实HAL和LL编程的逻辑与标准固件差不多。但是,缺点是,没有中文...
  • ARM指令集,C++标准库,C函数速查,C语言参考手册,...V2.0,STM32F4芯片手册STM32F103芯片中文教程及参考手册STM32F429开发指南-HAL库版本_V1.1,STM32库函数使用手册中文翻译版,UnixC函数,UNIX-C函数速查。

空空如也

空空如也

1 2
收藏数 33
精华内容 13
关键字:

stm32hal库中文手册