STM32基于CubeMX的HAL库开发

目录

串口

CubeMX配置

​编辑

串口发送

串口接收

串口空闲中断+DMA

CubeMx配置

库函数调用

串口闲时中断+DMA+环形缓冲区

CubeMx配置

环形缓冲区

函数调用

测试结果

独立看门狗

CubeMx配置

库函数调用

ADC

CubeMx配置

库函数调用

 DAC

 CubeMX的配置

代码部分:

定时器

CubeMx配置(使用中断的方法)

库函数调用

CubeMx配置(不使用中断的方法)

​编辑

库函数调用

PWM输出

PWM频率计算公式

公式应用:

CubeMx配置

库函数调用

PWM捕获

CubeMx配置

库函数调用

 测量2路频率 (PA15 R40 +  PB4 R39)

测量频率和占空比

Flash读写

CubeMx配置

库函数调用

IIC

CubeMx配置

库函数调用

SPI

CubeMx配置

库函数调用

RTC

CubeMx配置

库函数调用

FreeRTOS

CubeMX配置RTOS的步骤

如何选择最合适的栈大小

uxTaskGetStackHighWaterMark()API介绍

软件定时器的使用

CubeMx配置

库函数调用



串口

CubeMX配置

Asynchronous异步模式。如果需要接收,要开启中断。

设置波特率,校验位等

串口发送

串口发送的两种方法

1.

u8 tx_buf[] = {"hello,world!\r\n"};
HAL_UART_Transmit(&huart1,(unsigned char *)tx_buf,sizeof(tx_buf)-1,50);

2.重定向printf函数

//串口发送
int fputc(int ch, FILE *f) 
{
  HAL_UART_Transmit(&huart1,(unsigned char*)&ch,1,50);
  /* Your implementation of fputc(). */
  return ch;
}

printf("hello,world!\r\n");

串口接收

先开启串口接收中断

HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口中断

串口接收中断回调函数编写

/* 串口接收部分 */
uint8_t uart_buf[2] = {0};//接收串口接收的数据
uint8_t rx_buf[3] = {0};//保存串口接收的数据
uint8_t rx_cnt = 0;     
uint8_t rx_cnt_flag = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启下一次串口接收中断
    if(huart->Instance == USART1)//处理USART1接收到的数据
    {
        
        if( rx_cnt < 3 )// 检查当前接收索引是否在有效范围内
        {
            // 将接收到的数据存储到接收缓冲区中,并递增索引
            rx_buf[rx_cnt++] = uart_buf[0];
            if(rx_cnt >= 3)
                rx_cnt_flag = 1;
                
        } 
        
    }
    
}

串口数据处理函数

void Uart_Data_Process(void)
{
    if( (rx_cnt >= 3) && (rx_cnt_flag == 1) ) 
    {
        //执行对应的功能的代码
        /*******************************************************/
        
    

        /*******************************************************/
        rx_cnt = 0;//串口接收数组指针清零
        rx_cnt_flag = 0;//串口接收标志位清零
        memset(rx_buf,'\0',sizeof(rx_buf));//清空串口接收数组
        
    }
}


串口空闲中断+DMA

CubeMx配置

开启串口中断

在串口设置界面,点击DMA Settings。其他设置均为默认即可。

库函数调用

1.准备工作。启动DMA接收,关闭DMA传输过半中断。

  HAL_UARTEx_ReceiveToIdle_DMA(&huart1,uart_buf,sizeof(uart_buf));//启动DMA接收
  __HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);//关闭DMA传输过半中断

2.声明对应DMA句柄

extern DMA_HandleTypeDef hdma_usart1_rx;

3.编写串口DMA中断回调函数。串口DMA闲时中断,触发的是HAL_UARTEx_RxEventCallback

/* 串口接收相关变量 */
uint8_t uart_buf[50];//串口接收数组
uint8_t rx_buf[50];  //保存串口接收数据数组
uint8_t rx_cnt;
uint8_t rx_finish_flag = 0;//串口接收完成标志位

/* 串口DMA不定长数据收发 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if(huart == &huart1)
    {
        
        rx_cnt = Size;                           //读取接收数据的长度
        memcpy(rx_buf,uart_buf,sizeof(uart_buf));//将uart_buf的数据复制到rx_buf
        memset(uart_buf,0,sizeof(uart_buf));     //清空uart_buf的数据,以便下次接收      
        rx_finish_flag = 1;                      //串口接收完成标志位置1
        
         //将收到的数据回传
        HAL_UART_Transmit_DMA(&huart1,rx_buf,Size);
        
        /* 这里使用HAL_UARTEx)ReceiveToldle 或者 HAL_UARTEx_ReceiveToIdle_IT 也可以*/
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1,uart_buf,sizeof(uart_buf));//启动接收
        /* 当接收的数据量达到设置的最大值的一半时,也会触发一次HAL_UARTEx_RxEventCallback回调 */
        __HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);//关闭DMA传输过半中断
    }
      
}

功能代码参考

/* 串口数据处理 */
void Uart_Data_Process(void)
{
    
    if(rx_finish_flag)//串口数据接收完成
    {
        
        /* 功能代码 */
        if(rx_buf[0] == '0')//控制LED0
        {
            switch(rx_buf[1])
            {
                case '0':
                    HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET);
                break;
                
                case '1':
                    HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET);
                break;
            }
        }
        else if(rx_buf[0] == '1')//控制LED1
        {
            switch(rx_buf[1])
            {
                case '0':
                    HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
                break;
                
                case '1':
                    HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
                break;
            }
        }
        
        /*---------*/
        rx_finish_flag = 0;//串口接收完成标志位清零
    }
}


串口闲时中断+DMA+环形缓冲区

CubeMx配置

与前面步骤类似。

先配置DMA

开启中断

环形缓冲区

底层代码

.c文件

/**
 * @brief 初始化环形缓冲区
 *
 * 该函数用于初始化一个环形缓冲区,设置其读写指针的初始位置,
 * 清空缓冲区内的数据,并将项目计数初始化为0。
 *
 * @param rb 指向要初始化的环形缓冲区结构体的指针
 */
void ringbuffer_init(ringbuffer_t *rb)
{
    // 设置读指针和写指针初始值为0
    rb->r = 0;
    rb->w = 0;
    // 将缓冲区内存清零
    memset(rb->buffer, 0, sizeof(uint8_t) * RINGBUFFER_SIZE);
    // 初始化项目计数为0
    rb->itemCount = 0;
}

/**
 * @brief 检查环形缓冲区是否已满
 *
 * 该函数用于检查环形缓冲区是否已满。通过比较项目计数和缓冲区大小来判断。
 *
 * @param rb 指向环形缓冲区结构体的指针
 * @return uint8_t 返回1表示缓冲区已满,0表示未满
 */
uint8_t ringbuffer_is_full(ringbuffer_t *rb)
{
    // 如果项目计数等于缓冲区大小,返回1(已满),否则返回0(未满)
    return (rb->itemCount == RINGBUFFER_SIZE);
}

/**
 * @brief 检查环形缓冲区是否为空
 *
 * 该函数用于检查环形缓冲区是否为空。通过判断项目计数是否为0来确定。
 *
 * @param rb 指向环形缓冲区结构体的指针
 * @return uint8_t 返回1表示缓冲区为空,0表示非空
 */
uint8_t ringbuffer_is_empty(ringbuffer_t *rb)
{
    // 如果项目计数为0,返回1(为空),否则返回0(非空)
    return (rb->itemCount == 0);
}

/**
 * @brief 向环形缓冲区写入数据
 *
 * 该函数用于将数据写入环形缓冲区。如果缓冲区已满,则返回错误。
 * 否则,将数据依次写入缓冲区,并更新写指针和项目计数。
 *
 * @param rb 指向环形缓冲区结构体的指针
 * @param data 指向要写入的数据的指针
 * @param num 要写入的数据的字节数
 * @return int8_t 返回0表示写入成功,-1表示缓冲区已满写入失败
 */
int8_t ringbuffer_write(ringbuffer_t *rb, uint8_t *data, uint32_t num)
{
    // 如果缓冲区已满,返回-1
    if(ringbuffer_is_full(rb))
        return -1;
        
    // 将数据写入缓冲区
    while(num--)
    {
        rb->buffer[rb->w] = *data++;  // 写入数据并移动写指针
        rb->w = (rb->w + 1) % RINGBUFFER_SIZE;  // 写指针循环递增
        rb->itemCount++;  // 增加项目计数
    }
    
    return 0;  // 写入成功返回0
}

/**
 * @brief 从环形缓冲区读取数据
 *
 * 该函数用于从环形缓冲区读取数据。如果缓冲区为空,则返回错误。
 * 否则,从缓冲区中依次读取数据,并更新读指针和项目计数。
 *
 * @param rb 指向环形缓冲区结构体的指针
 * @param data 指向存储读取数据的缓冲区的指针
 * @param num 要读取的数据的字节数
 * @return int8_t 返回0表示读取成功,-1表示缓冲区为空读取失败
 */
int8_t ringbuffer_read(ringbuffer_t *rb, uint8_t *data, uint32_t num)
{
    // 如果缓冲区为空,返回-1
    if(ringbuffer_is_empty(rb))
        return -1;
    
    // 从缓冲区读取数据
    while(num--)
    {
        *data++ = rb->buffer[rb->r];  // 读取数据并移动读指针
        rb->r = (rb->r + 1) % RINGBUFFER_SIZE;  // 读指针循环递增
        rb->itemCount--;  // 减少项目计数
    }
    return 0;  // 读取成功返回0
}

.h文件

#ifndef RINGBUFFER_H
#define RINGBUFFER_H

#include "main.h"
#include <string.h>

#define RINGBUFFER_SIZE (30)  // 环形缓冲区大小定义

typedef struct {
    uint32_t w;            // 写指针,指示下一个写入位置
    uint32_t r;            // 读指针,指示下一个读取位置
    uint8_t buffer[RINGBUFFER_SIZE];  // 缓冲区数组
    uint32_t itemCount;    // 当前缓冲区中数据项数
} ringbuffer_t;  // 环形缓冲区结构体定义

void ringbuffer_init(ringbuffer_t *rb);  // 初始化环形缓冲区函数声明
uint8_t ringbuffer_is_full(ringbuffer_t *rb);  // 检查缓冲区是否已满函数声明
uint8_t ringbuffer_is_empty(ringbuffer_t *rb);  // 检查缓冲区是否为空函数声明
int8_t ringbuffer_write(ringbuffer_t *rb, uint8_t *data, uint32_t num);  // 写入数据函数声明
int8_t ringbuffer_read(ringbuffer_t *rb, uint8_t *data, uint32_t num);  // 读取数据函数声明

extern ringbuffer_t usart_rb;  // 全局环形缓冲区实例声明

#endif

函数调用

外部声明DMA句柄,定义相关缓冲区

extern DMA_HandleTypeDef hdma_usart1_rx;//声明DMA句柄

#define BUUFER_SIZE 64      //定义读取缓冲区大小

ringbuffer_t usart_rb;// 定义环形缓冲区
uint8_t usart_read_buffer[BUUFER_SIZE];// 定义读取缓冲区
uint8_t uart_rx_dma_buffer[128] = {0};//DMA接收缓存

初始化环形缓冲区,开启串口DMA接收以及关闭DMA传输过半中断

ringbuffer_init(&usart_rb);//初始化环形缓冲区
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,uart_rx_dma_buffer,sizeof(uart_rx_dma_buffer));//启动DMA接收
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);//关闭DMA传输过半中断

编写串口DMA中断回调函数

/**
 * @brief UART DMA接收完成回调函数
 *        将接收到的数据写入环形缓冲区,并清空DMA缓冲区
 * @param huart UART句柄
 * @param Size 接收到的数据大小
 * @retval None
 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    //测试:发送接收到的数据
    //printf("%s", uart_rx_dma_buffer);
    // 如果环形缓冲区未满
    if(!ringbuffer_is_full(&usart_rb))
    {
        // 将DMA缓冲区中的数据写入环形缓冲区
        ringbuffer_write(&usart_rb, uart_rx_dma_buffer, Size);
    }
    // 清空DMA缓冲区
    memset(uart_rx_dma_buffer, 0, sizeof(uart_rx_dma_buffer));
    
    /* 这里使用HAL_UARTEx)ReceiveToldle 或者 HAL_UARTEx_ReceiveToIdle_IT 也可以*/
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1,uart_rx_dma_buffer,sizeof(uart_rx_dma_buffer));//启动接收
    /* 当接收的数据量达到设置的最大值的一半时,也会触发一次HAL_UARTEx_RxEventCallback回调 */
    __HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);//关闭DMA传输过半中断
}

串口数据处理

/* 串口数据处理 */
/**
 * @brief  处理UART接收缓冲区中的数据。
 *         如果在100ms内没有接收到新的数据,将清空缓冲区。
 * @param  None
 * @retval None
 */
// 处理UART接收缓冲区中的数据
void Uart_Process(void)
{
    // 如果环形缓冲区为空,直接返回
    if(ringbuffer_is_empty(&usart_rb)) return;
    
    // 从环形缓冲区读取数据到读取缓冲区
    ringbuffer_read(&usart_rb, usart_read_buffer, usart_rb.itemCount);
    
    // 打印读取缓冲区中的数据
    printf("%s", usart_read_buffer);
    
    // 清空读取缓冲区
    memset(usart_read_buffer, 0, sizeof(uint8_t) * BUUFER_SIZE);
}

示例代码

/* 串口数据处理 */
/**
 * @brief  处理UART接收缓冲区中的数据。
 *         如果在100ms内没有接收到新的数据,将清空缓冲区。
 * @param  None
 * @retval None
 */
// 处理UART接收缓冲区中的数据
void Uart_Process(void)
{
    // 如果环形缓冲区为空,直接返回
    if(ringbuffer_is_empty(&usart_rb)) return;
    
    // 从环形缓冲区读取数据到读取缓冲区
    ringbuffer_read(&usart_rb, usart_read_buffer, usart_rb.itemCount);
    
    // 打印读取缓冲区中的数据.用于测试
//    printf("%s", usart_read_buffer);
    
     /*----------------------------------------------------功能代码-----------------------------------------*/
    if(usart_read_buffer[0] == '0')//控制LED0
    {
        switch(usart_read_buffer[1])
        {
            case '0':
                HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET);
            break;
            
            case '1':
                HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET);
            break;
        }
    }
    else if(usart_read_buffer[0] == '1')//控制LED1
    {
        switch(usart_read_buffer[1])
        {
            case '0':
                HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
            break;
            
            case '1':
                HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
            break;
        }
    }
        
    /*-----------------------------------------------------------------------------------------------------*/
    
    
    // 清空读取缓冲区
    memset(usart_read_buffer, 0, sizeof(uint8_t) * BUUFER_SIZE);
}

测试结果

发送11万多数据,数据丢失率极低。


独立看门狗

CubeMx配置

1.使能独立看门狗

2.设置超时时间。T = (分频值*重装载值)/独立看门狗时钟频率

这里T = 128*1250 / 40,000 = 4 s

独立看门狗的时钟频率可以通过时钟图查看。40Khz

库函数调用

看门狗初始化函数:

HAL_IWDG_Init(IWDG_HandleTypeDef *hiwdg)

喂狗函数:

HAL_IWDG_Refresh(IWDG_HandleTypeDef *hiwdg)
举例:  HAL_IWDG_Refresh(&hiwdg);   //看门狗喂狗

参考代码

.c文件

#include "my_iwdg.h"
#include <stdio.h>
#include "stm32f1xx_hal.h"
#include "main.h"
#include "iwdg.h"

/* 喂狗时间计数变量,在main.c中定义 */
extern uint32_t iwdg_time;

void Feed_Dog(void)
{
    if( (HAL_GetTick() - iwdg_time) > 3000)//每3S隔喂一次狗。独立看门狗的超时时间约为4S
    {
      printf("feed dog\r\n"); 
      HAL_IWDG_Refresh(&hiwdg);
      iwdg_time = HAL_GetTick();
    }
}

.h文件

#ifndef __MY_IWDG_H
#define __MY_IWDG_H

//函数声明
void Feed_Dog(void);

#endif

ADC

CubeMx配置

开启需要的ADC通道

库函数调用
HAL_ADC_Start(&hadc2);//开启ADC
HAL_ADC_GetValue(&hadc2);//获取ADC值

示例代码

//ADC
u16 adc2_val;
void ADC_Process()
{
  HAL_ADC_Start(&hadc2);
  adc2_val = HAL_ADC_GetValue(&hadc2);
  volt_r37 = adc2_val / 4096.0f*3.3f;
}

 DAC

 CubeMX的配置

以PA4,PA5为例

选择Connected 同external pin only 模式

其他不用改。

代码部分:

/* DAC */
float dac_volt1,dac_volt2;
void DAC_Process()
{
  //输出3.30V
  dac_volt1 = 3.30f;
  HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_1,DAC_ALIGN_12B_R,4095/(3.3/dac_volt1));//0-0V,4096-3.3V。默认12位右对齐
  HAL_DAC_Start(&hdac1,DAC1_CHANNEL_1);
  
  //输出1.11V
  dac_volt2 = 1.11f;
  HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_2,DAC_ALIGN_12B_R,4095/(3.3/dac_volt2));//0-0V,4096-3.3V。默认12位右对齐
  HAL_DAC_Start(&hdac1,DAC1_CHANNEL_2);
  
}


定时器

CubeMx配置(使用中断的方法)

此处定时1s。主频Fm=72M。f = 1hz = Fm / [ (PSC + 1) * (ARR + 1)]

时钟源现在内部时钟。设置PSC和ARR的值

 开启中断

库函数调用

1.开启定时器中断

HAL_TIM_Base_Start_IT(&htim2);

2.在tim.c中编写中断回调函数

/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        //功能代码
        LED_Toggle();
    }
}
/* USER CODE END 1 *

CubeMx配置(不使用中断的方法)

此处定时1s。主频Fm=72M。f = 1hz = Fm / [ (PSC + 1) * (ARR + 1)]

时钟源现在内部时钟。设置PSC和ARR的值

中断不勾选

库函数调用

1.首先,使用HAL_TIM_Base_Init()函数对定时器进行初始化,设置定时器的时钟源、预分频器、计数周期等参数

2.然后,使用HAL_TIM_Base_Start()函数启动定时器。该函数会启动定时器的计数功能,但不会开启中断

HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start(&htim2);

3.由于未开启中断,需要通过轮询的方式检查定时器的状态。可以使用__HAL_TIM_GET_FLAG()宏来检查定时器的更新事件标志(UEV)

  if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET) {
    // 清除标志位
    __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
    // 执行定时任务
         LED_Toggle(); 
    }

4.如果需要停止定时器,可以使用HAL_TIM_Base_Stop()函数

HAL_TIM_Base_Stop(&htim2);

PWM输出

PWM频率计算公式

公式应用:

如果我们要输出1000hz,占空比为50%的方波信号。

假如系统频率为80MHz = 80*10^6Hz。可以这样设置

PSC = 80 - 1,PSC + 1 = 80。ARR = 1000 - 1。ARR + 1 = 1000。

f = fsystem / [ (PSC+1)*(ARR+1) ] = 1000Hz

对于占空比,在CubeMx中则设置Pulse。

Pulse = 占空比*(ARR+1) = 50% * 1000  = 500

定时器溢出时间计算

Ft / ( PSC + 1 ) = 分频后的频率。( PSC + 1 )/ Ft = 数一个数的时间(

( PSC + 1 ) / Ft * ( ARR + 1 )  数ARR个数后溢出。

CubeMx配置

现在以PA7为例,设置PA7的PWM输出

中断不需要配置

参数设置

Prescaler 分频值设置为79(80-1),Counter Period 对应周期(us)。这里设置上电后默认PA7输出1KHz,占空比为20%。对应1000us.(0-999)。

Pulse(us):高电平时间。占空比20%对应为1000us * 20% = 200us

库函数调用

1.先开启PWM

//PWM输出
  HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);
 

编译后下载到板子上,经测试PA7上电后默认输出1000Hz,占空比为20%波形。

2.如果要在程序中修改PWM的输出参数(如占空比和输出频率)

//6.PWM输出。输出方波(占空比为50%)
void PWM_Process()
{
  
  if(volt_r37 > volt_r38)
  {
    TIM2->ARR = 100-1;//周期为100us(0-99)。输出10Khz,占空比50%
    TIM2->CCR2 = 50;//CCR对应占空比。(ARR+1)*50% = 50
  }
  else
  {
    TIM2->ARR = 5000-1;//周期为5000us(0-4999)。输出200hz,占空比50%
    TIM2->CCR2 = 2500;//高电平时间。(ARR+1)*50% = 2500。 5000us中有2500us为高电平.
  }
  
}

分析:

CNT为定时器中的计数器,(自动重装载寄存器)ARR寄存器存放的是周期(us),CCR寄存器存放的是高电平时间(us)。

举例:要设置输出1KHz的PWM方波信号(占空比50%),则ARR = 1000-1 = 999.

因为0-999us则为1000ms,对应的频率为1KHz。CCR = 500。1000ms * 50% = 500ms。


PWM捕获

CubeMx配置

以PA15为例

Channel1选择Input Capture direct mode。同时开启中断

参数设置

Prescaler设置为79(0-79 则为80)。时钟80MHz,分频后为1MHz,即1us计时一次。

Counter Period 设置成最大,尽量防止溢出。


 

库函数调用

开启PWM捕获

//PWM捕获
  HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//开启TIM2_CH1的输入捕获中断

参考代码

u32 tim2_cnt1 = 0;
u32 f40 = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  tim2_cnt1 = __HAL_TIM_GetCounter(&htim2);//获取CNT,单位us
  __HAL_TIM_SetCounter(&htim2,0);//设置CNT为0,重新开始计时
  
  //1S = 1e6us,f = 1/T
  f40 = 1e6 / tim2_cnt1;//R40调制的555定时器频率
  
  HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//每次都需要重新开启TIM2_CH1的输入捕获中断
  
}

 测量2路频率 (PA15 R40 +  PB4 R39)

开启中断

周期最大为65535us。可测量范围为:15Hz-1MHz

参数设置

这里的Counter Period 最大为0xFFFF(65535)

代码部分:

while(1)前:

HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);//开启TIM3_CH1的输入捕获中断

/* PWM捕获 */
u32 tim2_cnt1 = 0;
u32 f40 = 0;
u32 tim3_cnt1 = 0;
u32 f39 = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  //因为2路PWM捕获利用的是同一个中断函数,所以需要if进行判断
  
  if(htim == &htim2)
  {
    tim2_cnt1 = __HAL_TIM_GetCounter(&htim2);//获取CNT,单位us
    __HAL_TIM_SetCounter(&htim2,0);//设置CNT为0,重新开始计时
    
    f40 = 1e6 / tim2_cnt1;//R40调制的555定时器频率
    
    HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//每次都需要重新开启TIM2_CH1的输入捕获中断
  }
  
  if(htim == &htim3)
  {
    tim3_cnt1 = __HAL_TIM_GetCounter(&htim3);//获取CNT,单位us
    __HAL_TIM_SetCounter(&htim3,0);//设置CNT为0,重新开始计时
    
    f39 = 1e6 / tim3_cnt1;//R39调制的555定时器频率
    
    HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);//每次都需要重新开启TIM3_CH1的输入捕获中断
  }
}

如果碰到同一个定时器不同通道,还需对通道进行判断。

测量频率和占空比

基本思路:cnt1为高电平时间,cnt为周期  占空比= cnt1/cnt2。

代码部分:

/* PWM捕获 */
u32 tim2_cnt1 = 0, tim2_cnt2 = 0;//cnt1 为高电平时间 cnt2为周期
u32 f40 = 0;
float d40;
u32 tim3_cnt1 = 0,tim3_cnt2 = 0;//cnt1 为高电平时间 cnt2为周期
u32 f39 = 0;
float d39;
u8 tim2_state = 0;//0-开始计时 1-获取T1 2-获取T2 
u8 tim3_state = 0;//0-开始计时 1-获取T1 2-获取T2 
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  //因为2路PWM捕获利用的是同一个中断函数,所以需要if进行判断
  //tim2_channel 1
  if(htim == &htim2)
  {
    if(tim2_state == 0)//第一个上升沿产生,开始计时
    {
      __HAL_TIM_SetCounter(&htim2,0);//设置CNT为0,重新开始计时
      TIM2->CCER |= 0x02;//CC1P置为1,改为下降沿中断
      tim2_state = 1;
    }
    else if(tim2_state == 1)//获取T1(高电平时间),并改为上升沿中断
    {
      tim2_cnt1 = __HAL_TIM_GetCounter(&htim2);//获取T1(us)高电平时间
      TIM2->CCER &= ~0x02;//CC1P置为0,改为上升沿中断
      tim2_state = 2;
    }
    else if(tim2_state == 2)//第二个上升沿中断,获取T2(us)周期
    {
      tim2_cnt2 = __HAL_TIM_GetCounter(&htim2);//获取T1,单位us
      f40 = 1e6 / tim2_cnt2;//R40调整的555定时器频率
      d40 = tim2_cnt1*100.0f/tim2_cnt2;
      tim2_state = 0;
    }
    HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//每次都需要重新开启TIM2_CH1的输入捕获中断
  }
  
  //tim3_channel 1
  if(htim == &htim3)
  {
    if(tim3_state == 0)//第一个上升沿产生,开始计时
    {
      __HAL_TIM_SetCounter(&htim3,0);//设置CNT为0,重新开始计时
      TIM3->CCER |= 0x02;//CC1P置为1,改为下降沿中断
      tim3_state = 1;
    }
    else if(tim3_state == 1)//获取T1(高电平时间),并改为上升沿中断
    {
      tim3_cnt1 = __HAL_TIM_GetCounter(&htim3);//获取T1(us)高电平时间
      TIM3->CCER &= ~0x02;//CC1P置为0,改为上升沿中断
      tim3_state = 2;
    }
    else if(tim3_state == 2)//第二个上升沿中断,获取T2(us)周期
    {
      tim3_cnt2 = __HAL_TIM_GetCounter(&htim3);//获取T1,单位us
      f39 = 1e6 / tim3_cnt2;//R40调整的555定时器频率
      d39 = tim3_cnt1*100.0f/tim3_cnt2;
      tim3_state = 0;
    }
    HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);//每次都需要重新开启TIM2_CH1的输入捕获中断
  }
  
}


Flash读写

CubeMx配置

无需配置,默认开启。

库函数调用

stm32flash.h

#ifndef __STMFLASH_H
#define __STMFLASH_H




/* FLASH起始地址 */
#define STM32_FLASH_SIZE        0x80000         /* STM32 FLASH 总大小 */
#define STM32_FLASH_BASE        0x08000000      /* STM32 FLASH 起始地址 */

 /* STM32F103 扇区大小 */
#if STM32_FLASH_SIZE < 256 * 1024
#define STM32_SECTOR_SIZE   1024                /* 容量小于256K的 F103, 扇区大小为1K字节 */
#else
#define STM32_SECTOR_SIZE   2048                /* 容量大于等于于256K的 F103, 扇区大小为2K字节 */
#endif

/* FLASH解锁键值 */
#define STM32_FLASH_KEY1        0X45670123
#define STM32_FLASH_KEY2        0XCDEF89AB

/* 静态函数(仅限stmflash.c调用) */
static void stmflash_unlock(void);                  /* 解锁STM32 内部FLASH */
static void stmflash_lock(void);                    /* 锁定STM32 内部FLASH */
static uint8_t stmflash_get_error_status(void);     /* 获取FLASH错误状态 */
static uint8_t stmflash_wait_done(uint32_t time);   /* 等待操作完成 */
static uint8_t stmflash_erase_sector(uint32_t saddr);                   /* 擦除扇区 */
static uint8_t stmflash_write_halfword(uint32_t faddr, uint16_t data);  /* FLASH写半字 */

/* 接口函数(外部可调用) */
uint16_t stmflash_read_halfword(uint32_t faddr);                        /* FLASH读半字 */
void stmflash_read(uint32_t raddr, uint16_t *pbuf, uint16_t length);    /* 从指定地址开始读出指定长度的数据 */
void stmflash_write(uint32_t waddr, uint16_t *pbuf, uint16_t length);   /* 在FLASH 指定位置, 写入指定长度的数据(自动擦除) */

/* 测试函数 */
void test_write(uint32_t waddr, uint16_t wdata);

#endif

stm32flash.c

#include "stmflash.h"

/**
 * @brief       从指定地址读取一个半字 (16位数据)
 * @param       faddr   : 读取地址 (此地址必须为2的倍数!!)
 * @retval      读取到的数据 (16位)
 */
uint16_t stmflash_read_halfword(uint32_t faddr)
{
    return *(volatile uint16_t *)faddr;
}

/**
 * @brief       从指定地址开始读出指定长度的数据
 * @param       raddr : 起始地址
 * @param       pbuf  : 数据指针
 * @param       length: 要读取的半字(16位)数,即2个字节的整数倍
 * @retval      无
 */
void stmflash_read(uint32_t raddr, uint16_t *pbuf, uint16_t length)
{
    uint16_t i;

    for (i = 0; i < length; i++)
    {
        pbuf[i] = stmflash_read_halfword(raddr);    /* 读取2个字节 */
        raddr += 2; /* 偏移2个字节 */
    }
}

/**
 * @brief       不检查的写入
                这个函数的假设已经把原来的扇区擦除过再写入
 * @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param       pbuf    : 数据指针
 * @param       length  : 要写入的 半字(16位)数
 * @retval      无
 */
void stmflash_write_nocheck(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
    uint16_t i;
    for (i = 0; i < length; i++)
    {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, waddr, pbuf[i]);
        waddr += 2; /* 指向下一个半字 */
    }
}

/**
 * @brief       在FLASH 指定位置, 写入指定长度的数据(自动擦除)
 *   @note      该函数往 STM32 内部 FLASH 指定位置写入指定长度的数据
 *              该函数会先检测要写入的扇区是否是空(全0XFFFF)的?, 如果
 *              不是, 则先擦除, 如果是, 则直接往扇区里面写入数据.
 *              数据长度不足扇区时,自动被回擦除前的数据
 * @param       waddr   : 起始地址 (此地址必须为2的倍数!!,否则写入出错!)
 * @param       pbuf    : 数据指针
 * @param       length  : 要写入的 半字(16位)数
 * @retval      无
 */
uint16_t g_flashbuf[STM32_SECTOR_SIZE / 2]; /* 最多是2K字节 */
void stmflash_write(uint32_t waddr, uint16_t *pbuf, uint16_t length)
{
    uint32_t secpos;        /* 扇区地址 */
    uint16_t secoff;        /* 扇区内偏移地址(16位字计算) */
    uint16_t secremain;     /* 扇区内剩余地址(16位字计算) */
    uint16_t i;
    uint32_t offaddr;       /* 去掉0X08000000后的地址 */
    FLASH_EraseInitTypeDef flash_eraseop;
    uint32_t erase_addr;    /* 擦除错误,这个值为发生错误的扇区地址 */

    if (waddr < STM32_FLASH_BASE || (waddr >= (STM32_FLASH_BASE + STM32_FLASH_SIZE)))
    {
        return;     /* 非法地址 */
    }

    HAL_FLASH_Unlock();                         /* FLASH解锁 */

    offaddr = waddr - STM32_FLASH_BASE;         /* 实际偏移地址. */
    secpos = offaddr / STM32_SECTOR_SIZE;       /* 扇区地址  0~255 for STM32F103ZET6 */
    secoff = (offaddr % STM32_SECTOR_SIZE) / 2; /* 在扇区内的偏移(2个字节为基本单位.) */
    secremain = STM32_SECTOR_SIZE / 2 - secoff; /* 扇区剩余空间大小 */
    if (length <= secremain)
    {
        secremain = length; /* 不大于该扇区范围 */
    }

    while (1)
    {
        stmflash_read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 读出整个扇区的内容 */
        for (i = 0; i < secremain; i++)     /* 校验数据 */
        {
            if (g_flashbuf[secoff + i] != 0XFFFF)
            {
                break;      /* 需要擦除 */
            }
        }
        if (i < secremain)  /* 需要擦除 */
        { 
            flash_eraseop.TypeErase = FLASH_TYPEERASE_PAGES;        /* 选择页擦除 */
            flash_eraseop.Banks = FLASH_BANK_1;
            flash_eraseop.NbPages = 1;
            flash_eraseop.PageAddress = secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE;  /* 要擦除的扇区 */
            HAL_FLASHEx_Erase( &flash_eraseop, &erase_addr);

            for (i = 0; i < secremain; i++)                         /* 复制 */
            {
                g_flashbuf[i + secoff] = pbuf[i];
            }
            stmflash_write_nocheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE, g_flashbuf, STM32_SECTOR_SIZE / 2); /* 写入整个扇区 */
        }
        else
        {
            stmflash_write_nocheck(waddr, pbuf, secremain);         /* 写已经擦除了的,直接写入扇区剩余区间. */
        }
        if (length == secremain)
        {
            break; /* 写入结束了 */
        }
        else       /* 写入未结束 */
        {
            secpos++;               /* 扇区地址增1 */
            secoff = 0;             /* 偏移位置为0 */
            pbuf += secremain;      /* 指针偏移 */
            waddr += secremain * 2; /* 写地址偏移(16位数据地址,需要*2) */
            length -= secremain;    /* 字节(16位)数递减 */
            if (length > (STM32_SECTOR_SIZE / 2))
            {
                secremain = STM32_SECTOR_SIZE / 2; /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = length; /* 下一个扇区可以写完了 */
            }
        }
    }

    HAL_FLASH_Lock();   /* 上锁 */
}

/******************************************************************************************/
/* 测试用代码 */

/**
 * @brief       测试写数据(写1个字)
 * @param       waddr : 起始地址
 * @param       wdata : 要写入的数据
 * @retval      读取到的数据
 */
void test_write(uint32_t waddr, uint16_t wdata)
{
    stmflash_write(waddr, &wdata, 1);   /* 写入一个半字 */
}

#include "stmflash.h"

/* 要写入到STM32 FLASH的字符串数组 */
const uint8_t g_text_buf[] = {"STM32 FLASH TEST_HELLO"};

#define TEXT_LENTH sizeof(g_text_buf) /* 数组长度 */

/*SIZE表示半字长(2字节), 大小必须是2的整数倍, 如果不是的话, 强制对齐到2的整数倍 */
#define SIZE TEXT_LENTH / 2 + ((TEXT_LENTH % 2) ? 1 : 0)

#define FLASH_SAVE_ADDR 0X08070000 /* 设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小 + 0X08000000) */


int main(void)
{
    uint8_t key = 0;
    uint16_t i = 0;
    uint8_t datatemp[SIZE];

    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    usmart_dev.init(72);                /* 初始化USMART */
    led_init();                         /* 初始化LED */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */

    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "FLASH EEPROM TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write  KEY0:Read", RED);

    while (1)
    {
        key = key_scan(0);

        if (key == KEY1_PRES) /* KEY1按下,写入STM32 FLASH */
        {
            lcd_fill(0, 150, 239, 319, WHITE); /* 清除半屏 */
            lcd_show_string(30, 150, 200, 16, 16, "Start Write FLASH....", RED);
            stmflash_write(FLASH_SAVE_ADDR, (uint16_t *)g_text_buf, SIZE);
            lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", RED); /* 提示传送完成 */
        }

        if (key == KEY0_PRES) /* KEY0按下,读取字符串并显示 */
        {
            lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH.... ", RED);
            stmflash_read(FLASH_SAVE_ADDR, (uint16_t *)datatemp, SIZE);
            lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is:  ", RED); /* 提示传送完成 */
            lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE);       /* 显示读到的字符串 */
        }

        i++;
        delay_ms(10);

        if (i == 20)
        {
            LED0_TOGGLE(); /* 提示系统正在运行 */
            i = 0;
        }
    }
}

IIC

CubeMx配置
库函数调用

SPI

CubeMx配置

库函数调用

RTC

CubeMx配置

Activate Clock Source使能时钟源,Activate Calendar 使能日历

在时钟为 32KHz的情况下,Asynchronous Predivider value 设置为32-1。Synchronous Predivider value 设置为1000-1 。因为是从0开始计时,0-999为1000。 

Hour Format小时格式,这里设置为24小时制。我们的目的是设置1S计时一次,所以频率要设置为1Hz。

计算公式:

f = Fm / [ (Asynchronous Predivider value + 1) * (Synchronous Predivider value+1) ]

库函数调用

获取时分秒:HAL_RTC_GetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN);
获取年月日:HAL_RTC_GetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN);

HAL_RTC_GetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN);

参考代码

RTC_TimeTypeDef rtc_time;//包含时分秒
RTC_DateTypeDef rtc_date;//包含年月日星期

/* RTC实时时钟 */

void RTC_Process()
{
  HAL_RTC_GetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN);
  HAL_RTC_GetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN);
}


void LCD_Process()
{
  u8 display_buf[20];
  switch(display_mode)
  {
    case 0://界面1
    {
      sprintf((char*)display_buf,"     %02d-%02d-%02d  ",
      rtc_time.Hours,rtc_time.Minutes,rtc_time.Seconds);

      LCD_DisplayStringLine(Line9, (uint8_t *)display_buf);
      break;
    }
}


FreeRTOS

CubeMX配置RTOS的步骤

1.修改时钟源。基本时钟源要从Systick改为其他定时器。

2.版本选择 。选择V1版本,兼容性更强。

3.开启需要的相关功能。如xTaskDelayUntil(),uxTaskGetStackHighWaterMark()等。

4.任务的添加 。选择优先级,栈大小,任务创建方式等。

如何选择最合适的栈大小

在CuBeMX中使能uxTaskGetStackHighWaterMark。

uxTaskGetStackHighWaterMark()API介绍
  • 作用:获取任务堆栈的高水位标记(High Water Mark),即任务运行过程中未使用的最小堆栈空间量。

  • 单位:返回值以“字”为单位(例如,在32位系统中,1字 = 4字节)。

  • 参数

    • 任务句柄(TaskHandle_t)。传入NULL表示查询当前任务的堆栈高水位。

  • 用途

    • 检测任务堆栈的使用情况,判断堆栈是否接近溢出。

    • 优化任务堆栈分配,确保任务有足够的堆栈空间。

  • 配置:在 FreeRTOSConfig.h 中定义 INCLUDE_uxTaskGetStackHighWaterMark 为1,以启用该功能。

使用示例

/* 查看高水位值 */
uint16_t water_num = 0;
static uint8_t task1_water;
static uint8_t task2_water;
water_num = uxTaskGetStackHighWaterMark(my_task1Handle);
if(water_num >= task1_water)
   task1_water = water_num;
   printf("task1=%d\r\n",water_num);
      
water_num = uxTaskGetStackHighWaterMark(my_task2Handle);
if(water_num >= task2_water)
   task2_water = water_num;
   printf("task2=%d\r\n",water_num);

默认配置的栈大小为128。task1剩余的栈大小为34。task2剩余的栈大小为102。

task1实际使用的栈大小为128-34 = 94字节。task2实际使用的栈大小为128-102 = 26字节。

软件定时器的使用

CubeMx配置

使能软件定时器

在Timers处点Add

设置定时器参数。

此处设置一个周期定时器和单次定时器。

库函数调用

要使用定时器,得先开启定时器。

osTimerStart(myTimer01Handle,1000);
osTimerStart(myTimer02Handle,1000); 

 根据需求编写软件定时器对应的回调函数

 

串口观察现象。timer01为周期定时器,每隔1s打印一次。timer02为单次定时器,打印一次后不再打印。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值