STM32个人学习记录

本文详细介绍了STM32的GPIO操作,包括初始化、读写、翻转和锁定等功能,以及外部中断的处理。同时,讲解了位带操作以实现类似51单片机的GPIO控制。此外,还涵盖了定时器的基础知识,如定时器的启动、中断和DMA方式,以及PWM输出的工作原理和配置。最后提到了输入捕获功能,用于测量频率和脉宽。
摘要由CSDN通过智能技术生成

GPIO

常用函数

//函数初始化之后的引脚恢复成默认的状态
void HAL_GPIO_DeInit();

//读取引脚电平


//写入引脚电平
void HAL_GPIO_WritePin();

//翻转引脚电平
void HAL_GPIO_TogglePin();

//锁定引脚电平
void HAL_GPIO_LockPin();

//外部中断服务函数,清除中断标志位
void HAL_GPIO_EXTI_IRQHandler();

//外部中断回调函数,进入中断服务函数后会调用回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  //HAL_Delay(20);
  if(GPIO_Pin==KEY1_Pin)
  {
    while (HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET);
    
  }
  else if(GPIO_Pin==KEY2_Pin)
  {
    while (HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET);
    
  }
  else if(GPIO_Pin==KEY_UP_Pin)
  {
    while (HAL_GPIO_ReadPin(KEY_UP_GPIO_Port,KEY_UP_Pin)==GPIO_PIN_SET);

  }
}

位带操作

//M3内核

//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入
//M4内核

//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).M4同M3类似,只是寄存器地址变了.
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014 
#define GPIOJ_ODR_ADDr    (GPIOJ_BASE+20) //0x40022414
#define GPIOK_ODR_ADDr    (GPIOK_BASE+20) //0x40022814

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
#define GPIOJ_IDR_Addr    (GPIOJ_BASE+16) //0x40022410 
#define GPIOK_IDR_Addr    (GPIOK_BASE+16) //0x40022810 

//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

#define PJout(n)   BIT_ADDR(GPIOJ_ODR_Addr,n)  //输出 
#define PJin(n)    BIT_ADDR(GPIOJ_IDR_Addr,n)  //输入

#define PKout(n)   BIT_ADDR(GPIOK_ODR_Addr,n)  //输出 
#define PKin(n)    BIT_ADDR(GPIOK_IDR_Addr,n)  //输入

定时器

定时器中断

开启定时器函数
//阻塞模式
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim) //通过调用下面的宏完成.
__HAL_TIM_ENABLE(htim);

//中断方式
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim); //相比阻塞模式,多了使能中断的宏.
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);


//DMA方式,将pData搬运到ARR寄存器
HAL_StatusTypeDef HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length);

关闭同理,start换成stop

回调函数
//定时器中断回调函数,htimx是定时器句柄
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == &htimx)
    {
      
    }
}

PWM输出

原理介绍
重要的三个寄存器值
  • TIMx_CNT 计数器当前值
  • TIMx_ARR 自动重载值(上限)
  • TIMx_CRRx 捕获/比较寄存器 :在PWM模式下,当CNT小于CRR输出低电平,大于CRR输出高电平
具体过程

img

  • 定时器从0开始向上计数
  • 当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
  • t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
  • 当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数…循环此过程
  • 至此一个PWM周期完成
工作模式
  • PWM模式1:在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。
  • PWM模式2:在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。
  • 简单的说就是两个互补,模式1高电平,模式2就是低电平;模式2高电平,模式1就是低电平.
cubeMX选项解读
  1. MODE: 设置PWM模式
  2. Pulse: 设置占空比,即设置TIMx_CCR1寄存器的值。占空比=CCRx/ARRx
  3. Output compare preload: 称为输出比较预装载使能寄存器,如果enable,任何写入TIMx_CCRx事件到来,都不会打断当前计数周期,只能等到计完数了(更新事件),才把TIMx_CCRx传送至当前计数寄存器。如果disable,任何写入TIMx_CCRx事件到来,都会打断当前计数周期,TIMx_CCRx的值传送至当前计数寄存器。
  4. fast mode:
  5. Polarity 有效电平
  6. IDLE State空闲状态电平
  7. Dithering 抖动模式 可以增加PWM的分辨率。 参考文章
总结

ARR决定了PWM的周期,CRR决定了PWM的占空比.

输入捕获

1.应用:测频率,脉宽

2.工作原理:检测两次边沿所间隔时间

输入捕获代码

uint32_t capture_Buf[3] = {0};   //存放计数值
uint8_t capture_Cnt = 0;    //状态标志位
uint32_t high_time;   //高电平时间

//主函数循环
while (1)
{
    if (capture_flag == 3)
    {
      usb_printf("按键按下时间为%dms\n", (cnt_falling - cnt_rising) / 1000);
      OLED_ClrScr(0x00);
      OLED_printf(25, 25, &tFont12, "time:%dms", (cnt_falling - cnt_rising) / 1000);
      __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);
      HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
      capture_flag = 1;
    }
}

//回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim2)
  {
    if (capture_flag == 1)
    {
      cnt_rising = TIM2->CCR1;
      capture_flag++;
      __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
    }
    else if (capture_flag == 2)
    {
      cnt_falling = TIM2->CCR1;
      // HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);
      capture_flag++;
      HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_2);
    }
  }
}

函数

 __HAL_TIM_SET_COUNTER(&TIM5_Handler,0);   //设置计数寄存器的值变为0,不是函数,是底层的宏定义

void HAL_TIM_PWM_Start();              //函数用于使能定时器某一通道的PWM输出。

void HAL_TIM_IC_Start_IT(,);                  //函数用于使能定时器某一通道的输入捕获功能,并使能相应的中断

void HAL_TIM_IC_Stop_IT();                 //函数和开启功能相反,是关闭定时器某一通道的输入捕获功能和相应中断

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) //hui'd

串口

Printf重定向

#include <stdio.h>
#include <stdarg.h>
//---------keil microLIB-------------------------------------
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}

int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}
//-------------------------------------------------------
/*------------GCC----------------------*/
int _write (int fd, char *pBuffer, int size)
{
  for (int i = 0; i < size; i++)
  {
    while((USART1->ISR&(USART_ISR_TC))==0);   /*Wait for data transmission to complete (TC bit)*/

    USART1->TDR = (uint8_t) pBuffer[i];       /*write TDR*/
  }
  return size;
}

//多串口实现printf,并添加函数声明

void usart2_printf(const char *format,...)
{
  uint8_t usart2_txBuf[200];
	uint16_t len;
	va_list args;	
	va_start(args,format);
	len = vsnprintf((char*)usart2_txBuf,sizeof(usart2_txBuf)+1,(char*)format,args);
	va_end(args);
	HAL_UART_Transmit(&huart2, usart2_txBuf, len,0xff);
}

串口发送

主要使用三个函数,分别是阻塞发送、中断发送、DMA发送

HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

关于中断发送的理解参考文章:串口中断发送理解

中断发送和DMA发送结束后均会有完成中断,回调函数为HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)

串口接收

阻塞接收方式

使用HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)函数直接查询收到的数据,如:

while (1)
  {
    if(HAL_UART_Receive(&huart1,UART1_RX_BUF,1,1000)==HAL_OK)
    {
      UART1_RX_BUF[0]++;
      HAL_UART_Transmit(&huart1,UART1_RX_BUF,1,1000);
    }
  }
中断方式
使用HAL库回调函数

主要是HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)函数实现中断接收.

大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断,再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)

流程图如下(正点原子):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D6YyfBzP-1682271639946)(C:\Users\木月\AppData\Roaming\Typora\typora-user-images\image-20220926105947560.png)]

使用时要先调用一次HAL_UART_Receive_IT()函数,目的是开启接收中断,并初始化要接收的数据指向,数据长度。

然后回调函数中的代码执行完毕后也要调用一次HAL_UART_Receive_IT()函数,原因是HAL_UART_Receive_IT()函数只触发一次中断。

采用这种方式时,当接收到一定长度时才发生中断,所以这种方式一般用于处理定长数据。

#define USART1_RX_MAXLEN 255
uint8_t usart1_rx_buf[USART1_RX_MAXLEN];

//先调用一次这个函数
HAL_UART_Receive_IT(&huart1,usart1_rx_buf,8);


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //在这个函数写中断后你想干的事
{
  if (huart==&huart1)
  {
    
    HAL_UART_Receive_IT(&huart1,usart1_rx_buf,8);
  }
}
中断服务函数直接接收
//初始化时要先使能中断,例如可在usart.c中的HAL_UART_MspInit函数中添加接收中断使能
    /* USER CODE BEGIN USART1_MspInit 1 */
    __HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);		//开启接收中断
    /* USER CODE END USART1_MspInit 1 */

//在stmf4xx_it.c中添加下面的代码,并且在stmf4xx_it.h中添加外部变量声明

/* USER CODE BEGIN PV */
#define USART_REC_LEN 255
u8 USART1_RX_BUF[USART_REC_LEN];     
u16 USART1_RX_STA=0;       
/* USER CODE END PV */

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
 
  /*USART1_RX_STA为接收完成标志位,当USART1_RX_STA为0x8000表示接收完成.
    Res为接收数据临时缓存,接收到数据时判断是否为结束位转存给USART8_RX_BUF*/
  
  u8 Res;
  if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET))  //读取SR寄存器中的RXNE位,1表示收到数据可以读出
	{
    HAL_UART_Receive(&huart1,&Res,1,1000); //将接收到的数据转存至Res
		if((USART1_RX_STA&0x8000)==0)
		{
			if(USART1_RX_STA&0x4000)
			{
				if(Res!=0x0a)USART1_RX_STA=0;
				else USART1_RX_STA|=0x8000;	
			}
			else
			{	
				if(Res==0x0d) 
          USART1_RX_STA|=0x4000;
				else
				{
          USART1_RX_BUF[USART1_RX_STA&0X3FFF]=Res;//收到数据就转存到RX_BUF,直到收到0x0d
          USART1_RX_STA++;
          if(USART1_RX_STA>(USART_REC_LEN-1))USART1_RX_STA=0; //数据超出长度
				}		 
			}
		}   		 
	}
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}


//调用代码如下
if(USART8_RX_STA&0x8000) 
{
  
    
  USART8_RX_STA=0;
}
DMA接收

使用方法与中断方式类似,函数为HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

IDLE接收空闲中断+DMA

前面写的接收方式都只能用于定长数据,或者是通过帧结尾来判断一帧数据的结束,而无法判断不定长的数据。

IDLE介绍

IDLE是USART_SR寄存器的第3位,在stm32中文手册中是这么介绍的:

当检测到总线空闲时,该位被硬件置位。如果USART_CR1中的IDLEIE为’1’,则产生中断。由 软件序列清除该位(先读USART_SR,然后读USART_DR)。

0:没有检测到空闲总线;

1:检测到空闲总线。

注意: IDLE位不会再次被置高直到RXNE位被置起(即又检测到一次空闲总线)

也就是说,在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断.

比如给上位机给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。

函数

hal库提供了空闲中断的函数,可以直接调用

HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle();
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_IT();
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA();
//形参size表示最大接收到的字节数,最好让这个数大些。

__HAL_DMA_GET_COUNTER(&hdma_usart1_rx) //这个宏的作用是返回DMA传输剩余的字节数,所以也可以使用设置的总的接收字节数-这个宏返回的数来表示实际接收到的字节数
  
//回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  if (huart==&huart1)
  {
    
    HAL_UARTEx_ReceiveToIdle_IT();
  }
}
//形参size表示实际接受到的字节数

参考文章:https://blog.csdn.net/as480133937/article/details/105013368

使用时中断方式和DMA方式同样要先在主函数中调用一次

USB虚拟串口

发送
#include <stdio.h>
#include <stdarg.h>

//用usb虚拟串口发送函数为
extern uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);

void usb_printf(const char *format,...)
{
  uint8_t usb_txBuf[200];
	uint16_t len;
	va_list args;	
	va_start(args,format);
	len = vsnprintf((char*)usb_txBuf,sizeof(usb_txBuf)+1,(char*)format,args);
	va_end(args);
	CDC_Transmit_FS(usb_txBuf, len);
}
//接收使用CDC_Receive_FS()函数
接收
  1. usbd_cdc_if.c文件中定义变量uint32_t data_len;

  2. CDC_Receive_FS()函数中添加data_len=*Len;

    static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
    {
      /* USER CODE BEGIN 6 */
    
      USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
      USBD_CDC_ReceivePacket(&hUsbDeviceFS);
      data_len=*Len;
    
      return (USBD_OK);
      /* USER CODE END 6 */
    }
    

    接收数据长度为data_len,数据会存储在UserRxBufferFS这个数组中.

  3. 使用示例

        if (data_len>0)
        {
          //while(CDC_Transmit_FS(UserRxBufferFS,data_len));
          CDC_Transmit_FS(UserRxBufferFS,data_len);
          HAL_UART_Transmit(&huart1,UserRxBufferFS,data_len,100);
          data_len=0;
        }
    
  4. 说明

    • CDC_Receive_FS()实际上是中断回调函数,形参分别指向数据和长度指针,也就是说,它仍处于中断,所以最好不要在里面做太多事。
    • usbd_cdc_if.c的91和94行定义了供用户使用的数据缓存。

FIFO模式

FIFO( First Input First Output) 简单说就是指先进先出。它是一种数据存储或传输模式,可以硬件实现也可以软件实现,大多数用来缓存数据,减少操作次数,提高传输效率。

  • 当启用发送FIFO(TXfifo)时,写入传输数据寄存器(USART_TDR)的数据在TXFIFO中排队。

SPI

具体原理见文章

SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。

传输过程

SPI的时序

SPI特性

SPI总线包括4条逻辑线,定义如下:

  • MISOMaster input slave output 主机输入,从机输出(数据来自从机);

  • MOSIMaster output slave input 主机输出,从机输入(数据来自主机);

  • SCLKSerial Clock 串行时钟信号,由主机产生发送给从机;

  • SSSlave Select 片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号。

其他制造商可能会遵循其他命名规则,但是最终他们指的相同的含义。以下是一些常用术语;

  • MISO也可以是SIMODOUTDOSDOSO(在主机端);

  • MOSI也可以是SOMIDINDISDISI(在主机端);

  • NSS也可以是CECSSSEL;

  • SCLK也可以是SCK;

工作模式

在这里插入图片描述

img

ADC

img

注意事项

  1. 使用TRGO触发AD采集时,不能开启连续采样(Continuous Conversion Mode),否则会造成采样率异常
  2. 时钟来自APB2(最高90M),可设置分频,不要让ADC频率超过36M,否则可能会出现精度异常(429)

DAC

函数

HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef* hdac, uint32_t Channel);     //开启DAC输出
HAL_StatusTypeDef HAL_DAC_Stop(DAC_HandleTypeDef* hdac, uint32_t Channel);   //关闭DAC输出
HAL_StatusTypeDef HAL_DAC_Start_DMA(DAC_HandleTypeDef* hdac, uint32_t Channel, uint32_t* pData, uint32_t Length, uint32_t Alignment); //需要函数中不断开启   //开启DAC的DMA输出
HAL_StatusTypeDef HAL_DAC_Stop_DMA(DAC_HandleTypeDef* hdac, uint32_t Channel); //关闭DAC的DMA输出
HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef* hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data);  //设置DAC输出值
uint32_t HAL_DAC_GetValue(DAC_HandleTypeDef* hdac, uint32_t Channel);  //获取DAC输出值

使用

配置为输出直流电压
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2048);
HAL_DAC_Start(&hdac,DAC_CHANNEL_1);

//配置为输出三角波
HAL_TIM_Base_Start(&htim2);
HAL_DAC_Start(&hdac, DAC_CHANNEL_2);

//通过DMA传输开启输出
HAL_TIM_Base_Start(&htim2);//开启定时器2
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_1,(uint32_t*)Sine12bit,100,DAC_ALIGN_12B_R);

注意事项

三角波工作原理

首先设置一个DAC最大幅值,之后设置定时器溢出时间(TRGO),在每次TRGO信号到来后,DAC内部的三角波计数器加1 然后与DAC_DHRx寄存器的值相加,写到DAC_DORx计数器中,如果该值小于设定的最大幅值,就会正常输出,当大于最大幅值时就会递减,减到0之后又开始累加,周而复始,就形成了三角波

三角波频率

三角波频率 = 定时器频率 / (设置的最大幅值 ∗ 2 ) 三角波频率=定时器频率/(设置的最大幅值*2) 三角波频率=定时器频率/(设置的最大幅值2

CUBEMX

Output Buffer:输出缓存
  1. Enable:使能输出缓存,DAC的输出阻抗会降低,无需外部运放即可直接驱动外部负载。但是输出的电压没法低于20mv。如果不需要输出小于20mv的信号,一般开启输出缓存。
  2. disable:不使能输出缓存,那么DAC可以输出低于20mv的信号。
DMA工作方式区别
  1. Circular:循环模式,当DMA搬运完成一组数据后,会回到这组数据的起始位置,再次开启搬运,不断循环。DAC要输出连续的波形,就需要不断搬移。
  2. Normal:正常模式,DMA搬运完一组数据后,就不再搬运了。如果让DAC的DMA工作在这个模式它的波形就只有一个周期。

12bit正弦表

const uint16_t Sine12bit[100]=
{	
0x0800,0x0881,0x0901,0x0980,0x09FD,0x0A79,0x0AF2,0x0B68,0x0BDA,0x0C49,
0x0CB3,0x0D19,0x0D79,0x0DD4,0x0E29,0x0E78,0x0EC0,0x0F02,0x0F3C,0x0F6F,
0x0F9B,0x0FBF,0x0FDB,0x0FEF,0x0FFB,0x0FFF,0x0FFB,0x0FEF,0x0FDB,0x0FBF,
0x0F9B,0x0F6F,0x0F3C,0x0F02,0x0EC0,0x0E78,0x0E29,0x0DD4,0x0D79,0x0D19,
0x0CB3,0x0C49,0x0BDA,0x0B68,0x0AF2,0x0A79,0x09FD,0x0980,0x0901,0x0881,
0x0800,0x077F,0x06FF,0x0680,0x0603,0x0587,0x050E,0x0498,0x0426,0x03B7,
0x034D,0x02E7,0x0287,0x022C,0x01D7,0x0188,0x0140,0x00FE,0x00C4,0x0091,
0x0065,0x0041,0x0025,0x0011,0x0005,0x0001,0x0005,0x0011,0x0025,0x0041,
0x0065,0x0091,0x00C4,0x00FE,0x0140,0x0188,0x01D7,0x022C,0x0287,0x02E7,
0x034D,0x03B7,0x0426,0x0498,0x050E,0x0587,0x0603,0x0680,0x06FF,0x077F,
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值