> 技术文档 > 学习记录 STM32_stm32中断向量表

学习记录 STM32_stm32中断向量表


中断

中断的总体框图,总体架构:

STM32的中断有十个内核中断以及60个外设中断

对于数据手册得到解析

手册内容的解析

优先级:这个是中断事件对应的本身由硬件决定的优先级 当抢占优先级和响应优先级都相同时是由这个优先级决定中断的执行顺序

最后一个 地址:这个地址是中断向量表里面每当中断触发跳转的地址

中断向量表

中断向量表本身时一个数组

中断向量表的作用:当中断被触发时由于硬件的原因 程序会跳转到一个固定的位置 这个位置就是中断向量表里面的某个位置,这个位置里面存放着  中断服务函数的地址以及跳转到终端服务函数地址的程序 

所以中断触发并执行的大致流程是 :

发生中断  ->  程序跳转到相应中断在中断向量表的位置  ->根据这个位置里面的内容跳转到相应中断服务函数的地址并执行中断服务函数 -> 返回中断点继续执行主程序

NVIC

NVIC主要用于所有中断的管理一般为三个来源:来自内核的外设(系统滴答定时器,复位等),片上外设(定时器 串口 IIC等) EXTI (来自IO口 来自AFIO)

NVIC时STM32的中断管理器:支持256个中断(16+240)

NVIC主要用于优先级分组控制,中断优先级判断,中断的使能和使能

优先级分组控制:主要用于分几位给响应优先级 分几位给抢占优先级

中断优先级判断:当多个中断触发时 通过判断优先级来对中断服务函数的先后顺序进行处理

相关寄存器

中断使能寄存器/中断使能寄存器 (ISER/ICER):32位 八个每个位控制一个中断

应用程序中断寄存器(AIRCR):里面的其中三位可以控制优先级分组

中断优先级寄存器 (IPR) :八个位对应一个中断 但是STM32只使用高四位

EXTI

EXTI是外设中断控制器,EXTI可以监听GPIO的电平变化,并在指定条件时向NVIC提出中断申请

EXTI支持的触发方式:上升沿 下降沿 双边沿或者软件触发

EXTI在触发时同一个Pin不能同时触发PA0和PB0

单串口重定向发送数据

第一步

在魔术棒里面把Target中的Use Micro LIB 勾选 然后OK

在初始化USART之后

打开usart.c

先在代码前面包含stdio.h 

在代码的最后添加重定向函数

重定向函数

#ifdef __GNUC__#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)#else#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)#endifPUTCHAR_PROTOTYPE{ HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,HAL_MAX_DELAY); return ch;}

测试: 

串口的中断模式

轮询模式的底层机制:

串口的内部存在这么两个寄存器 分别为发送数据寄存器和发送移位寄存器

工作流程:数据经过发送数据寄存器之后,如果发送移位寄存器为空 就把数据传递到发送移位寄存器 然后会将数据转换成波特率下对应的高低电平从引脚发送出去 

在这个过程中CPU会不断检测发送数据寄存器是否为空 如果为空就把数据放入发送数据寄存器 直到没有数据可以发

在接收的过程中也存在接受数据寄存器和接受移位寄存器

串口中断模式:

通过以上的工作流程我们可以知道:再发送和接收这一整个过程中 CPU需要一直不断检测是否有数据在发送数据寄存器或者接受数据寄存器 这样子的话会很占用CPu的资源 因此需要使用串口的中断模式 

具体的中断:当发送数据寄存器或者接收数据寄存器为空时会触发中断 把执行其他程序的CPu叫回来去填放数据填放完之后就可以回去继续执行被中断的程序了

void HAL_UART_RxCpItCallback和void HAL_UART_TxHalfCpltCallBack

这两个函数会在串口接受和发送完全部数据之后才会执行这两个函数的内容

注意在用HAL_UART_RxCpItCallback这个回调函数的过程当中会自动关闭接收中断 所以如果想要连续的进行接收数据 要在HAL_UART_RxCpItCallback的最后开启下一次接收中断 HAl_UART_receive_IT

DMA 串口

可以为CPU创建一个搬运数据的助手 使得减少数据搬运对CPu带来大的消耗

所以可以创建一个DMA通道 告诉DMA把数据从哪里搬运到哪里 DMA会在合适的实际对数据进行搬运 等数据搬运完毕再通过中断告诉CPU数据传输完成

使用DMA简单的参数设置

Direction:方向

Memory To Peripheral  从内存到外设 即从内存到寄存器 

Peripheral To  Memory  从外设到内存 即从寄存器到内存

Increment Address: 是否进行地址的自增

Data WIdth :数据宽度 

具体函数 : HAL_UART_Transmit_DMA(&huart1,receiveData,2)

第一个参数时哪一个串口 第二个参数是发送的数据 第三个参数是字节数

这个函数大致的流程是:将内存地址里面存放receiveData的地址和串口一的发送数据寄存器联系起来 

如何接收一个不定长的数据

接收不定长的数据 适用于STM32接收不定长的数据 并且使用场景是当你和其他设备进行串口通信时 会有通信协议的存在 比如说帧头 帧尾 数据关键帧 

使用的方法是DMA+串口空闲中断

首先配置DMA

然后我们就可以直接打开工程

启动串口函数

HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,sizeof(rx_buffer));

第一个参数:串口

第二个参数:存放数据的缓存区

第三个参数代表接收大小 这里的大小指的不是每次接收的大小 适当写得大一些即可

书写中断服务函数:

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size){if(huart==&huart1){//HAL_UART_Transmit_DMA(&huart1,rx_buffer,Size);if(rx_buffer[0]==0x53&&rx_buffer[1]==0x59&&rx_buffer[2]==0x80&&rx_buffer[8]==0x54&&rx_buffer[9]==0x43){if(rx_buffer[7]==0x2E){printf(\"People no \\r\\n\");}else if(rx_buffer[7]==0x2F){printf(\"People IN \\r\\n\");}else{ printf(\"Other Data \\r\\n\");}}HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,sizeof(rx_buffer));__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);}

也就是说每次接受一次数据帧 进入中断处理一次数据

在每次处理完 你需要再次启动串口空闲DMA传输

注意点:这个中断服务函数在接收一半Size的时候也会触发所以要再main的While之前和每次开启之前去失能这个触发__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT);

在这次实验过程中 我还遇到了一个小错误:

在我调用__HAL_DMA_DISABLE_IT(&hdma_usart1_rx,DMA_IT_HT)时,第一个Handle我没有办法找到 所以需要在main.h里面去extern一下.

systick以及HAL_Delay底层

systick实际上就是一个滴答定时器,是一个具有自动重装载的24位的倒计时器

可以设置重装载值到计数器当计数器倒计时为零时  会有标志位变化 可以选择是否触发中断

Systick相关寄存器介绍:

systick控制及状态寄存器(CTRL):

16:COUNTFLAG: 这个位用来读取状态 

2:CLKSOURCE: 这个位用来选择计数器的时钟频率 0外部时钟源(8分频)1 不分频

1:  TICKINT : 该位用于倒计时到零时 是否产生异常请求 1产生异常请求 0无动作

0: ENABLE: systick定时器使能位

LOAD:systick寄存器重装载寄存器:这个寄存器的前二十四位用来写重装载值的大小

HAL_Delay底层逻辑

首先我们可以看下HAL_Delay的内容是在做什么

__weak void HAL_Delay(uint32_t Delay){ uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay; /* Add a freq to guarantee minimum wait */ if (wait < HAL_MAX_DELAY) { wait += (uint32_t)(uwTickFreq); } while ((HAL_GetTick() - tickstart) < wait) { }}

可以看到HAL_Delay中用到最多的就是HAL_GetTick()

接下来我们可以看下HAL_GetTick()的内容

HAL_GetTick主要是得到uwTick的值

而uwTick的变化主要产生在HAL_IncTick这个函数里面

通过搜索我们可以发现是在SysTick的中断服务函数里面被调用

而systick的中断是1ms触发一次因此

所以HAL_Delay是记录下当前的uwTIck的值 而uwTIck是1ms+1所以当时间间隔小于我们设置的值时程序会卡死在while这个位置

 while ((HAL_GetTick() - tickstart) < wait)

那么systick的中断为什么是1ms触发一次呢

这个需要在HAL_Init里面找到答案

定时器

基本定时器(TIM6,TIM7):这个定时器只能向上计数 只能计时不能对外输出脉冲

所以基本定时器就是用于更新中断 当成一个闹钟使用

DMA

DMA 直接存储器访问 提供外设与内存 存储器到存储器 外设与外设之间的高速数据传输使用 可以很好的为CPU减负

DMA基本工作流程

当有外设需要DMA帮忙搬运数据时外设会相对应的DMA发送DMA请求

当有多个外设发送请求时 DMA的仲裁器会分辨优先级 优先级高的先执行

外设对应DMA通道的表格

一个通道一次只能搬运一个外设的数据

DMA优先级管理

每个通道的优先级可以在DMA_CCRx寄存器中设置 分为最高级,高级,中级,低级

当软件优先级相同时 硬件会通过通道来判断优先级 通道数越低优先级越高

DMA传输方式

DMA_Mode_Normal:正常模式 当DMA出发之后DMA搬运数据一次就停止

DMA_Mode_Circular:循环传输模式当DMA触发之后 DMA会不停的搬运数据

DMA指针自增模式

分为源自增和目标自增

DMA对齐方式:主要是源和目标位数不同导致的

DMA相关寄存器

中断状态寄存器主要用到的是TCIFx这个位 通过观察这个位可以看到DMA传输是否完成

每当一次DMA传输结束会触发DMA中断状态寄存器的TCIF位置一 但是如果要进入下一次的DMA数据搬运传输就需要把上一次的DMA中断状态寄存器的TCIFx位置零 这时DMA中断标志清除寄存器就起到作用 主要是关注CTCIFx这个位 这个位置一可以清楚中断状态寄存器的TCIFx位。

DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)

这个寄存器是DMA里面相对最重要的寄存器

他可以配置

DMA通道的优先级

DMA数据源和目标的传输方向

DMA数据源和目标的位数

DMA数据传输是否开启循环模式

DMA目标和源地址是否自增

是否使能DMA传输完成中断

这两个寄存器就是目标和源的地址的寄存器

ADC

简单介绍ADC

ADC:模拟数字转换器 将模拟电压转换为数字量存储在内存中

ADC是12位精度(4095)

ADC工作流程:注入组4个通道 规则组16个通道  当触发控制时 会根据寄存器里面 规则组和注入组的顺序进行检测 每检测完一个通道就把测量结束的测量值放入数据寄存器 每放入一次会触发EOC一次和DMA请求一次 

ADC工作时钟频率来自PCLK2(72MHZ) ADC工作频率最高14mhz因此分频选择6/8

ADC通道和引脚对应的表

注入组和规则组的说明:

注入组和规则组检测的顺序以及一共检测多少个通道都是可以通过寄存器去设置的

同一个通道可以多次进行检测

ADC触发转换的方法:

通过向控制寄存器ADON位写1来开启ADC然后将SWSTART位置一 (软件触发)

也可通过外部事件对ADC检测进行触发(详细见开发手册)

ADC转换时间:采样时间+12.5个周期

采样时间可以根据寄存器去配置  周期事件就是经过PCLK分频之后的事件

单次转化和连续转换

单次转换:只转换你所选中的通道一次

连续转换:转换完你所选中的通道一次后立刻开启下一次转换

扫描模式和非扫描模式

多通道扫描模式+连续转换+DMA请求工作流程:

HAL_ADC_Start()触发采集-通道1采集到数据-存放到数据寄存器-产生DMA请求-数据搬运走-通道2采集数据-存放到数据寄存器-产生DMA请求-数据搬运走...-最后一个通道采集数据-存放到数据寄存器-产生DMA请求-数据搬运走-通道1采集到数据-存放到数据寄存器-产生DMA请求-数据搬运走.......不会停止除非手动停止ADC采集HAL_ADC_Stop.

多通道扫描模式+单次转换+DMA请求工作流程:
触发采集-通道1采集到数据-存放到数据寄存器-产生DMA请求-数据搬运走-通道2采集数据-存放到数据寄存器-产生DMA请求-数据搬运走...-最后一个通道采集数据-存放到数据寄存器-产生DMA请求-数据搬运走 到这里就会停止采集 如果需要继续采集需要再次触发采集HAL_ADC_Start().

关于ADC结构体的成员变量的说明:

ADC单通道读取数据(不带ADC):

基本流程:配置ADC工作参数 - ADC 校准  -  MSP初始化(NVIC CLOCK GPIO)- 配置ADC通道 - 启动ADC转换 - 等待ADC采样-读取ADC的值。  

Version1:使用纯HAL库去配置:

#include \"adc.h\"ADC_HandleTypeDef adc_handle = {0};void adc_init(void){ adc_handle.Instance = ADC1; adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; adc_handle.Init.ContinuousConvMode = DISABLE; adc_handle.Init.NbrOfConversion = 1; adc_handle.Init.DiscontinuousConvMode = DISABLE; adc_handle.Init.NbrOfDiscConversion = 0; adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; HAL_ADC_Init(&adc_handle); HAL_ADCEx_Calibration_Start(&adc_handle);}//初始化ADC并且开启ADC校准void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc){ if(hadc->Instance == ADC1) { RCC_PeriphCLKInitTypeDef adc_clk_init = {0}; GPIO_InitTypeDef gpio_init_struct = {0}; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); gpio_init_struct.Pin = GPIO_PIN_1; gpio_init_struct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &gpio_init_struct); adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); }}//配置ADC相关的时钟GPIO口的参数void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime){ ADC_ChannelConfTypeDef adc_ch_config = {0}; adc_ch_config.Channel = ch; adc_ch_config.Rank = rank; adc_ch_config.SamplingTime = stime; HAL_ADC_ConfigChannel(hadc, &adc_ch_config);} //通过这个函数可以去设置检测哪个adc的哪个通道以及该通道的采样时间uint32_t adc_get_result(uint32_t ch){ adc_channel_config(&adc_handle, ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5); HAL_ADC_Start(&adc_handle); HAL_ADC_PollForConversion(&adc_handle, 10); return (uint16_t)HAL_ADC_GetValue(&adc_handle);} //开启ADC并且等待ADC检测 得到ADC检测的值

main调用检测电压:

int main(void){ HAL_Init(); /* ³õʼ»¯HAL¿â */ stm32_clock_init(RCC_PLL_MUL9); /* ÉèÖÃʱÖÓ, 72Mhz */ led_init(); /* ³õʼ»¯LEDµÆ */ uart1_init(115200); adc_init(); printf(\"hello world!\\r\\n\"); while(1) { printf(\"adc result: %f\\r\\n\", (float)adc_get_result(ADC_CHANNEL_1) / 4096 * 3.3); delay_ms(500); }}

Version2:用CubeMX直接配置ADC
新建工程 进行基础配置之后先将ADC的时钟配置为12MHZ

第二步选择要使用的ADC以及对应的通道

在进行单通道无DMA检测时可以默认配置即可.

随后可以直接生成工程文件.

随后我们看到adc.c文件看看里面缺少什么配置 补充一下即可.

#include \"adc.h\"/* USER CODE BEGIN 0 *//* USER CODE END 0 */ADC_HandleTypeDef hadc1;/* ADC1 init function */void MX_ADC1_Init(void){ /* USER CODE BEGIN ADC1_Init 0 */ /* USER CODE END ADC1_Init 0 */ ADC_ChannelConfTypeDef sConfig = {0}; /* USER CODE BEGIN ADC1_Init 1 */ /* USER CODE END ADC1_Init 1 */ /** Common config */ hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN ADC1_Init 2 */ /* USER CODE END ADC1_Init 2 */}void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle){ GPIO_InitTypeDef GPIO_InitStruct = {0}; if(adcHandle->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspInit 0 */ /* USER CODE END ADC1_MspInit 0 */ /* ADC1 clock enable */ __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**ADC1 GPIO Configuration PA0-WKUP ------> ADC1_IN0 */ GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN ADC1_MspInit 1 */ /* USER CODE END ADC1_MspInit 1 */ }}void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle){ if(adcHandle->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspDeInit 0 */ /* USER CODE END ADC1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_ADC1_CLK_DISABLE(); /**ADC1 GPIO Configuration PA0-WKUP ------> ADC1_IN0 */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0); /* USER CODE BEGIN ADC1_MspDeInit 1 */ /* USER CODE END ADC1_MspDeInit 1 */ }}

根据基本流程:配置ADC工作参数 - ADC 校准  -  MSP初始化(NVIC CLOCK GPIO)- 配置ADC通道 - 启动ADC转换 - 等待ADC采样-读取ADC的值。 

可以看到当中缺少了ADC校准 以及启动ADC转换 - 等待ADC采样-读取ADC的值.

所以我们补充代码在main函数里面 需要使用的时候直接调用即可

在初始化的代码之后添加ADC校准

随后书写获取ADC的值的代码

float Get_MyAdc_Value(ADC_HandleTypeDef* hadc,uint32_t ADC_TimeOut){HAL_ADC_Start(hadc);HAL_ADC_PollForConversion(hadc,ADC_TimeOut);uint32_t adcdata1=HAL_ADC_GetValue(hadc);float data=(float)adcdata1/4096*3.3;return data;}

需要使用的时候调用该函数即可.

ADC单通道读取数据带DMA.

相比于不带DMA带DMA检测只是对了DMA初始化这个流程.

基本流程:配置ADC工作参数 - ADC 校准  -配置DMA 将DMA于ADC句柄联系起来-  MSP初始化(NVIC CLOCK GPIO)- 配置ADC通道 - 启动ADC转换 - 等待ADC采样-读取ADC的值。

Version1:纯HAL库版本.

#include \"adc.h\"ADC_HandleTypeDef adc_handle = {0};DMA_HandleTypeDef dma_handle = {0};void adc_config(void){ adc_handle.Instance = ADC1; adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; adc_handle.Init.ContinuousConvMode = ENABLE; adc_handle.Init.NbrOfConversion = 1; adc_handle.Init.DiscontinuousConvMode = DISABLE; adc_handle.Init.NbrOfDiscConversion = 0; adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; HAL_ADC_Init(&adc_handle); HAL_ADCEx_Calibration_Start(&adc_handle);}void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc){ if(hadc->Instance == ADC1) { RCC_PeriphCLKInitTypeDef adc_clk_init = {0}; GPIO_InitTypeDef gpio_init_struct = {0}; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); gpio_init_struct.Pin = GPIO_PIN_1; gpio_init_struct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &gpio_init_struct); adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); }}void dma_config(void){ __HAL_RCC_DMA1_CLK_ENABLE(); dma_handle.Instance = DMA1_Channel1; dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; //ÄÚ´æÏà¹ØÅäÖà dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; dma_handle.Init.MemInc = DMA_MINC_ENABLE; //ÍâÉèÏà¹ØÅäÖà dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; dma_handle.Init.Mode = DMA_CIRCULAR; HAL_DMA_Init(&dma_handle); __HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);}void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime){ ADC_ChannelConfTypeDef adc_ch_config = {0}; adc_ch_config.Channel = ch; adc_ch_config.Rank = rank; adc_ch_config.SamplingTime = stime; HAL_ADC_ConfigChannel(hadc, &adc_ch_config);}void adc_dma_init(uint32_t *mar){ adc_config(); adc_channel_config(&adc_handle, ADC_CHANNEL_1, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5); dma_config(); HAL_ADC_Start_DMA(&adc_handle, mar, 1);}

Version2:CubeMX 配置需要自己进行校准 需要自己指定DMA终点内存

几个配置的关键点:

Mode设置为normal模式 

方向是从外设到内存

数据长度都是一个字节

设置内存地址自增 外设地址不自增

main.c

/* USER CODE BEGIN Header *//** ****************************************************************************** * @file  : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** *//* USER CODE END Header *//* Includes ------------------------------------------------------------------*/#include \"main.h\"#include \"adc.h\"#include \"dma.h\"#include \"usart.h\"#include \"gpio.h\"/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include \"stdio.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 */#define ADC_BUFFER_SIZE 256 //volatile uint32_t adc_buffer[ADC_BUFFER_SIZE];volatile uint8_t adc_test_state =0;float ADC_Data=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_DMA_Init(); MX_USART1_UART_Init(); MX_ADC1_Init(); /* USER CODE BEGIN 2 */ HAL_ADCEx_Calibration_Start(&hadc1); ADC_Data=0;uint32_t ADC_DMA=0;HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_buffer,ADC_BUFFER_SIZE);//Get_DMA_ADC_Value(&hadc1,&ADC_DMA,1); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 *///printf(\"123123\\n\");//printf(\"ADC_Channel_0 Data is %f\\n\",(float)ADC_DMA/4096*3.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}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {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(); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC; PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); }}/* USER CODE BEGIN 4 */float Get_MyAdc_Value(ADC_HandleTypeDef* hadc,uint32_t ADC_TimeOut){HAL_ADC_Start(hadc);HAL_ADC_PollForConversion(hadc,ADC_TimeOut);uint32_t adcdata1=HAL_ADC_GetValue(hadc);float data=(float)adcdata1/4096*3.3;return data;}void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc){ uint32_t sum=0;for(int i=0;i<ADC_BUFFER_SIZE;i++){sum=sum+adc_buffer[i];}//ADC_Data=(sum/(float)ADC_BUFFER_SIZE)*3.3f/4096.0f;printf(\"ADC_Channel_0 Data is %f\\n\",(sum/(float)ADC_BUFFER_SIZE)*3.3f/4096.0f);HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_buffer,ADC_BUFFER_SIZE);} /* 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 */ __disable_irq(); while (1) { } /* 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, ex: printf(\"Wrong parameters value: file %s on line %d\\r\\n\", file, line) */ /* USER CODE END 6 */}#endif /* USE_FULL_ASSERT */

代码细节以及逻辑讲解

设置一个数组去存放测量到的电压值,最终得到的结果就是数组里面每一个值求和之后 取平均

关键函数讲解:HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)

第一个是哪个adc 第二个参数是数组 第三个是数组的大小 也就是ADC搬运的次数

也就是说当这个函数启用的时候会启动ADC和DMA DMA会从ADC的数据寄存器里面去搬运数据Length次 当达到Length次后会触发中断。进void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)这个中断服务函数里面

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc){ uint32_t sum=0;for(int i=0;i<ADC_BUFFER_SIZE;i++){sum=sum+adc_buffer[i];}//ADC_Data=(sum/(float)ADC_BUFFER_SIZE)*3.3f/4096.0f;printf(\"ADC_Channel_0 Data is %f\\n\",(sum/(float)ADC_BUFFER_SIZE)*3.3f/4096.0f);HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adc_buffer,ADC_BUFFER_SIZE);}

这个中断服务函数的代码逻辑:我们从ADC数据寄存器里面搬运了ADC_BUFFER_SIZE次数据分别存放在adc_buffer数组的0-ADC_BUFFER_SIZE 这些里面。我们对它求和之后取平均值就是对当前通道测量到的电压。随后又开启了HAL_ADC_Start_DMA 进行下一次测量 下一次测量结束之后又会进入中断服务函数进行数据处理。

IIC

IIC支持一主多从的通信 一共有两条总线 SCL SDA

SCL:时钟线 一般由主设备去控制 

SDA:数据位 发送数据

  • 支持不同速率的通讯速度,标准速度(最高速度100kHZ),快速(最高400kHZ)

  • SCL和SDA都需要接上拉电阻 (大小由速度和容性负载决定一般在3.3K-10K之间) 保证数据的稳定性,减少干扰。

IIC同一时间只支持单向传输 是半双工 不是全双工

模拟IIC传输一个数据帧的过程:开始信号->设备地址->从设备存储数据的地址->存储数据的值。

基本协议:

1.空闲状态

SCL和SDA两者都是高电平

2.开始信号

SCL保持高电平 SDA由高跳变成低电平

注意:为了使开始信号之后可以开始发送数据和接收数据 在开始信号发生之后 一般会把SCl拉低

3.停止信号

SCL保持高电平 SDA由低变高

4.传输数据

传输数据的有效性:注意当SCL为高电平的时候 不可以随便改变SDA上面的电平变化,如果在SCL高电平期间随意的去改变SDA 会被误以为认作开始信号和结束信号