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输出高电平
具体过程
- 定时器从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选项解读
MODE
: 设置PWM模式Pulse
: 设置占空比,即设置TIMx_CCR1
寄存器的值。占空比=CCRx/ARRxOutput compare preload
: 称为输出比较预装载使能寄存器,如果enable,任何写入TIMx_CCRx事件到来,都不会打断当前计数周期,只能等到计完数了(更新事件),才把TIMx_CCRx传送至当前计数寄存器。如果disable,任何写入TIMx_CCRx事件到来,都会打断当前计数周期,TIMx_CCRx的值传送至当前计数寄存器。fast mode
:Polarity
有效电平IDLE State
空闲状态电平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()函数
接收
-
在
usbd_cdc_if.c
文件中定义变量uint32_t data_len;
-
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
这个数组中. -
使用示例
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; }
-
说明
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总线包括4条逻辑线,定义如下:
-
MISO:
Master input slave output
主机输入,从机输出(数据来自从机); -
MOSI:
Master output slave input
主机输出,从机输入(数据来自主机); -
SCLK :
Serial Clock
串行时钟信号,由主机产生发送给从机; -
SS:
Slave Select
片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号。
其他制造商可能会遵循其他命名规则,但是最终他们指的相同的含义。以下是一些常用术语;
-
MISO也可以是
SIMO
,DOUT
,DO
,SDO
或SO
(在主机端); -
MOSI也可以是
SOMI
,DIN
,DI
,SDI
或SI
(在主机端); -
NSS也可以是
CE
,CS
或SSEL
; -
SCLK也可以是
SCK
;
工作模式
ADC
注意事项
- 使用TRGO触发AD采集时,不能开启连续采样(Continuous Conversion Mode),否则会造成采样率异常
- 时钟来自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:输出缓存
- Enable:使能输出缓存,DAC的输出阻抗会降低,无需外部运放即可直接驱动外部负载。但是输出的电压没法低于20mv。如果不需要输出小于20mv的信号,一般开启输出缓存。
- disable:不使能输出缓存,那么DAC可以输出低于20mv的信号。
DMA工作方式区别
- Circular:循环模式,当DMA搬运完成一组数据后,会回到这组数据的起始位置,再次开启搬运,不断循环。DAC要输出连续的波形,就需要不断搬移。
- 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,
};