目录
uxTaskGetStackHighWaterMark()API介绍
串口
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为单次定时器,打印一次后不再打印。