</syntaxhighlight>
== 8 10-串口打印实验[编辑 | 编辑源代码] ==此实验给大家展示的是利用STM32L476的I2C外设功能,去获取sht20温湿度传感器采集的温湿度数据。并且将获取到的数据转换成真实的温湿度数据,格式化打印到串口显示。
=== 8.1 STM32L476 I2C简介[编辑 | 编辑源代码] UART简介 ===I2C(内部集成电路)总线接口处理STM32L4和串行I2C总线之间的通信。 它提供多主机功能,并控制所有I2C总线特定的排序,协议,仲裁和定时。 它支持标准模式(Sm),快速模式(Fm)和快速模式加(Fm +)。兼容SMBus(系统管理总线)和PMBus(电源管理总线)。
I2C主要功能:''<span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span>''=== 硬件设计 ===选择STM32L4引脚PC6用来捕获红外传感器HS0038的DATA引脚输出的PWM波。''<span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span>''[[文件:NBDK-SCH-IR.png|边框|居中|无框|393x393像素|''<span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span>'']]''<span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span>''=== 实验准备 ===# 使用miniUSB线及10pin排线,通过Jlink仿真器连接PC端和开发板。# 使用miniUSB线,连接PC与开发板USB接口。# 将SW1拨到DBG端,SW2拨到MCU。# 使用Keil打开基础实验 09-红外线接收实验工程。# 使用Xshell打开Jlink虚拟出的COM口# 下载程序,并完成功能测试。
•I2C总线规范rev03兼容性:=== 实验验证 ===下载完成后,我们按下遥控器上的任意按键,可以看到LCD上将显示如下,irBtnVal代表的是键值,irBtnCnt代表是按键被按下的次数,irBtnInfo代表按键的图标或者定义。
- 从模式和主模式=== 源码详解 ===本节中的源码说明,仅针对此例程中的重要功能,详细的源码介绍请大家参照代码后的注释。
==== stm32l4xx_hal_conf.h ====此文件位于“09- 多主机功能红外线接收实验\Inc”路径中,主要用途是选择使能此例程使用到的库文件。
- 标准模式(最高100 kHz)此例程我们主要给大家展示STM32L4的I2C功能,所以我们宏定义中打开I2C相关的。<syntaxhighlight lang="c" line="1" start="103">// 使能的宏#define HAL_MODULE_ENABLED // 芯片#define HAL_FLASH_MODULE_ENABLED // Flash#define HAL_PWR_MODULE_ENABLED // 电源#define HAL_RCC_MODULE_ENABLED // 时钟#define HAL_CORTEX_MODULE_ENABLED // NVIC
- 快速模式(最高400 kHz)#define HAL_GPIO_MODULE_ENABLED // GPIO#define HAL_UART_MODULE_ENABLED // UART#define HAL_DMA_MODULE_ENABLED // DMA #define HAL_ADC_MODULE_ENABLED // ADC#define HAL_DAC_MODULE_ENABLED // DAC#define HAL_I2C_MODULE_ENABLED // I2C</syntaxhighlight>
- 快速模式加(最高1 MHz)==== main.c ====main函数,我们的例程由此处开始执行,首先调用HAL_Init()函数初始化我们的模块,接着调用SystemClock_Config()函数初始化此例程用到的时钟,具体有哪些时钟被初始化,在gyu_util.c部分有详细说明。
- 7位和10位寻址模式接下来我们初始化LCD的SPI控制引脚,LCD背光引脚,并且初始化LCD的图形控制界面。
- 多个7位从机地址(2个地址,1个带可配置掩码)然后我们在LCD上打印固定的遥控器按键显示格式,也就是"irBtnVal"、"irBtnCnt"、"irBtnInfo"这几个字符串。
- 所有7位地址确认模式接下来我们初始化我们此实验的重点功能,也就是TIM3定时器。
- 一般电话在while()循环中,我们轮询遥控器的按键信息,一旦有遥控器按下,则在LCD的对应位置,打印按键的信息。
- 可编程设置和保持时间<syntaxhighlight lang="c++" line="1" start="34">int main(void){ irInfo_t irkey = {0,0}; /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ // 重置所有外设、flash界面以及系统时钟 HAL_Init();
// 配置系统时钟(包含振荡器、系统时钟、总线时钟等等) SystemClock_Config(); // LCD SPI初始化 LCD_GPIO_Init(); // LCD IO控制引脚(例如背光) MX_SPI1_Init(); // LCD SPI控制引脚 // 图形界面初始化 GUI_Init(); // GUI界面初始化 GUI_Clear(); // 清屏 GUI_SetColor(GUI_Crimson); // 红色字体 GUI_DispStringAt("irBtnVal:",24,24); // 打印字符串"irBtnVal:"到位置X- 易于使用的事件管理>24,Y->24 GUI_DispStringAt("irBtnCnt:",24,72); // 打印字符串"irBtnCnt:"到位置X->24,Y->96 GUI_DispStringAt("irBtnInfo:",24,120); // 打印字符串"irBtnCnt:"到位置X->24,Y->120
// 初始化TIM3 MX_TIM3_Init(); // while(1) { irkey = IRBNT_POLL(); // 轮训获取IR按键信息 if(irkey.irBtnVal) // 如果按键信息存在 { GUI_DispHexAt(irkey.irBtnVal,144,24,2); // 打印按键值 GUI_DispHexAt(irkey.irBtnCnt,144,72,2); // 打印按键计数 // 打印按键图标或名称 GUI_GotoXY(144,120); // 指的光标位置 GUI_ClearArea(); // 清除指定位置数据 switch(irkey.irBtnVal) // 判断按键值,打印相应图标或名称 { case REMOTE_BTN_SWITCH: GUI_DispString("'switch'"); break; case REMOTE_BTN_MENU: GUI_DispString("'menu'"); break; case REMOTE_BTN_MUTE: GUI_DispString("'mute'"); break; case REMOTE_BTN_MODE: GUI_DispString("'mode'"); break; case REMOTE_BTN_PLUS: GUI_DispString("'+'"); break; case REMOTE_BTN_RETURN: GUI_DispString("'return'"); break; case REMOTE_BTN_REWIND: GUI_DispString("'|<<'"); break; case REMOTE_BTN_PAUSE: GUI_DispString("'>||'"); break; case REMOTE_BTN_FASTFORWARD: GUI_DispString("'>>|'"); break; case REMOTE_BTN_0: GUI_DispString("'0'"); break; case REMOTE_BTN_LESS: GUI_DispString("'- 可选的时钟拉伸'"); break; case REMOTE_BTN_OK: GUI_DispString("'OK'"); break; case REMOTE_BTN_1: GUI_DispString("'1'"); break; case REMOTE_BTN_2: GUI_DispString("'2'"); break; case REMOTE_BTN_3: GUI_DispString("'3'"); break; case REMOTE_BTN_4: GUI_DispString("'4'"); break; case REMOTE_BTN_5: GUI_DispString("'5'"); break; case REMOTE_BTN_6: GUI_DispString("'6'"); break; case REMOTE_BTN_7: GUI_DispString("'7'"); break; case REMOTE_BTN_8: GUI_DispString("'8'"); break; case REMOTE_BTN_9: GUI_DispString("'9'"); break; } } }}</syntaxhighlight>==== gyu_util.c ====时钟初始化函数,用于配置我们模块运行的系统时钟、AHB高性能总线时钟、APB外设总线时钟以及单个外设的时钟。
- 软件重置主要包含了三个部分的初始化配置。
•具有DMA功能的1字节缓冲区1.内部或者外部振荡器选择,也就是选择时钟信号的来源,是内部振荡,还是外部晶振。
•可编程模拟和数字噪声滤波器2.时钟配置,选择系统、AHB总线及APB总线的时钟来源。
=== 83.2 硬件设计[编辑 | 编辑源代码] ===选择STM32L4引脚PB13作为I2C SCL引脚,PB14作为I2C SDA引脚。外设时钟配置,选择外设时钟来源。
=== 8为了给大家比较全面的展示各个时钟,我们振荡器选择HSI(内部16MHz高频)、HSE(外部8MHz高频)以及LSE(外部32.3 实验准备[编辑 | 编辑源代码] ===# 使用miniUSB线及10pin排线,通过Jlink仿真器连接PC端和开发板。# 使用miniUSB线,连接PC与开发板USB接口。# 将SW1拨到DBG端,SW2拨到MCU。# 使用Keil打开基础实验 07-温湿度实验工程。# 使用Xshell打开Jlink虚拟出的COM口# 下载程序,并完成功能测试。768KHz低频)三个。选择HSE作为PLL(锁相回路)时钟源,配置PLLCLK为80MHz。配置系统时钟SYSCLK、AHB高性能总线、APB外设总线(APB1及APB2)为80MHz。另外我们还分别配置了ADC、UART以及I2C的外设时钟。
基础实验中的其他例程,大部分都是使用的相同的时钟配置函数,有特殊的时钟使用,将会在对应例程的源码详解中做针对性说明。<syntaxhighlight lang="c++" line="1" start= 8.4 实验验证[编辑 | 编辑源代码] ==="49">void SystemClock_Config(void){ RCC_OscInitTypeDef RCC_OscInitStruct; // 定义RCC内部/外部振荡器结构体 RCC_ClkInitTypeDef RCC_ClkInitStruct; // 定义RCC系统,AHB和APB总线时钟配置结构体 RCC_PeriphCLKInitTypeDef PeriphClkInit; // 定义RCC扩展时钟结构体 // 配置LSE驱动器功能为低驱动能力下载完成后,打开COM口,可以看到每隔500ms打印一次采集到的温湿度数据。 __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW);
在采集的过程中,我们将手指按在SHT20上,可以看到温度和湿度都在上升,例如温度,由开始的20 // 初始化CPU,AHB和APB总线时钟 RCC_OscInitStruct.4°C上升到最终的26OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE |RCC_OSCILLATORTYPE_LSE; // 设置需要配置的振荡器为HSI、HSE、LSE // 配置HSE RCC_OscInitStruct.7°C。HSEState = RCC_HSE_ON; // 激活HSE时钟(开发板外部为8MHz) // 配置LSE RCC_OscInitStruct.LSEState = RCC_LSE_ON; // 激活LSE时钟(32.768KHz,低驱动) // 配置HSI RCC_OscInitStruct.HSIState = RCC_HSI_ON; // 激活HSI时钟 RCC_OscInitStruct.HSICalibrationValue = 16; // 配置HSI为16MHz // 配置PLL RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开PLL RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 选择HSE时钟作为PLL入口时钟源,8MHz RCC_OscInitStruct.PLL.PLLM = 1; // 配置PLL VCO输入分频为1,8/1 = 8MHz RCC_OscInitStruct.PLL.PLLN = 20; // 配置PLL VCO输入倍增为20,8MHz*20 = 160MHz RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // SAI时钟7分频,160/7 = 22.857143MHz RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // SDMMC、RNG、USB时钟2分频,160/2 = 80MHz RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // 系统主时钟分区2分频,160/2 = 80MHz // RCC时钟配置,出错则进入错误处理函数 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } // 初始化CPU,AHB和APB总线时钟 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; // 需要配置的时钟HCLK、SYSCLK、PCLK1、PCLK2 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 配置系统时钟为PLLCLK输入,80MHz RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟为系统时钟1分频,80/1 = 80MHz RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1时钟为系统时钟1分频,80/1 = 80MHz RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2时钟为系统时钟1分频,80/1 = 80MHz // RCC时钟配置,出错则进入错误处理函数 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) // HCLK=80MHz,Vcore=3.3V,所以选择SW4(FLASH_LATENCY_4) { _Error_Handler(__FILE__, __LINE__); }
// 初始化外设时钟 PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_USART2 |RCC_PERIPHCLK_LPUART1|RCC_PERIPHCLK_LPTIM1 |RCC_PERIPHCLK_I2C2|RCC_PERIPHCLK_ADC; // 需要初始化的外设时钟:USART1、USART2、LPUART1、LPTIM1、I2C2、ADC PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; // 配置串口USART1时钟为PCLK2,80MHz PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1; // 配置串口USART2时钟为PCLK1,80MHz PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_HSI; // 配置LPUART时钟为HSI,16MHz PeriphClkInit.I2c2ClockSelection = RCC_I2C2CLKSOURCE_PCLK1; // 配置I2C2时钟为PCLK1,80MHz PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE; // 配置LPTIM1时钟为LSE,32.768KHz PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1; // 配置ADC时钟为PLLSAI1,现在为80MHz,下面会重新定义 PeriphClkInit.PLLSAI1.PLLSAI1Source =RCC_PLLSOURCE_HSE; // 配置PLLSAI1时钟为HSE,8MHz PeriphClkInit.PLLSAI1.PLLSAI1M =1; // 配置PLLSAI1分频为1 PeriphClkInit.PLLSAI1.PLLSAI1N = 8; // 配置PLLSAI1倍增为8 PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7; // SAI时钟7分频,64/7 = 9.142857MHz PeriphClkInit.PLLSAI1.5 源码详解[编辑 | 编辑源代码] PLLSAI1Q = RCC_PLLQ_DIV2; // SDMMC、RNG、USB时钟2分频,64/2 =32MHz PeriphClkInit.PLLSAI1.PLLSAI1R =RCC_PLLR_DIV2; // 系统主时钟分区2分频,64/2 =32MHz PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_ADC1CLK; // 配置PLLSAI1输出为ADC1时钟,也就是配置ADC1时钟,32MHz // 外设时钟配置,出错则进入错误处理函数 if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { _Error_Handler(__FILE__, __LINE__);本节中的源码说明,仅针对此例程中的重要功能,详细的源码介绍请大家参照代码后的注释。 }
==== 8 // 配置内部主稳压器输出电压,配置为稳压器输出电压范围1模式,也就是:典型输出电压为1.5.1 stm32l4xx_hal_conf.h[编辑 | 编辑源代码] ===2V,系统频率高达80MHz if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) !=HAL_OK) { _Error_Handler(__FILE__, __LINE__);此文件位于“07-温湿度实验\Inc”路径中,主要用途是选择使能此例程使用到的库文件。 }
此例程我们主要给大家展示STM32L4的I2C功能,所以我们宏定义中打开I2C相关的。 103 // 使能的宏配置系统定时器中断时间,配置为HCLK的千分频 104 #define HAL_MODULE_ENABLED // 芯片 105 #define HAL_FLASH_MODULE_ENABLED // Flash 106 #define HAL_PWR_MODULE_ENABLED // 电源 107 #define HAL_RCC_MODULE_ENABLED // 时钟 108 #define HAL_CORTEX_MODULE_ENABLED HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()// NVIC 109 110 #define HAL_GPIO_MODULE_ENABLED // GPIO 111 #define HAL_UART_MODULE_ENABLED // UART 112 #define HAL_DMA_MODULE_ENABLED // DMA 113 #define HAL_ADC_MODULE_ENABLED // ADC 114 #define HAL_DAC_MODULE_ENABLED // DAC 115 #define HAL_I2C_MODULE_ENABLED // I2C1000);
==== 8.5.2 main.c[编辑 | 编辑源代码] ==== // 配置系统定时器,配置为HCLKmain函数,我们的例程由此处开始执行,首先调用HAL_Init HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK)函数初始化我们的模块,接着调用SystemClock_Config()函数初始化此例程用到的时钟,具体有哪些时钟被初始化,在gyu_util.c部分有详细说明。;
接下来我们初始化了串口部分,目的是打印采集到的温湿度数据。 // 系统定时器中断配置,设置系统定时器中断优先级最高(为0),且子优先级最高(为0) HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);}</syntaxhighlight>
接下来初始化I2C引脚。==== gyu_irc ====初始化定时器TIM3,首先配置TIM3的时钟为1MHz(也就是1us),我们设置它向上自动装载,并且设置自动装载值为10000,通过计算可以知道装满一次需要10ms。
在while()循环中,我们每隔500ms采集一次温湿度的值,并且将采集的温湿度值转化成真实值,格式化打印到串口显示。 33 int main(void) 34 { 35 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 36 // 重置所有外设、flash界面以及系统时钟 37 HAL_Init(); 38 39 // 配置系统时钟(包含振荡器、系统时钟、总线时钟等等) 40 SystemClock_Config(); 41 42 // 初始化串口USART1 43 MX_USART1_UART_Init(); 44 45 // 初始化I2C2 46 MX_I2C2_Init(); 47 48 // 49 while (1) 50 { 51 HAL_Delay(500); 52 printf("Temp = %.1f\r\n",SHT20_Convert(SHT20_ReadTemp(),1)); 53 printf("RH = %.1f%%\r\n",SHT20_Convert(SHT20_ReadRH(),0)); 54 } 55 }然后我们需要设置输入捕获的参数,我们配置上升沿下降沿都捕获,并且设置8个时钟周期的滤波(防止误识别)。
==== 8.5.3 gyu_util.c[编辑 | 编辑源代码] ====时钟初始化函数,用于配置我们模块运行的系统时钟、AHB高性能总线时钟、APB外设总线时钟以及单个外设的时钟。最后使能TIM3的中断,并且开始捕获TIM3的通道1(也就是PC6引脚)。
主要包含了三个部分的初始化配置。<syntaxhighlight lang="c++" line="1" start="59">void MX_TIM3_Init(void){ htim3.Instance = TIM3; // 通用定时器3 htim3.Init.Prescaler = 80-1; // TIM3 80预分频器(APB2总线),80MHz / 80 = 1MHz(1us) htim3.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数器 htim3.Init.Period = 10000; // 自动装载值设为10000,装满一次 10000 * 1us = 10ms htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;// 不分频 htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 自动加载使能 if (HAL_TIM_IC_Init(&htim3) != HAL_OK) // 初始化TIM3,出错则进入错误处理函数 { _Error_Handler(__FILE__, __LINE__); } // 初始化TIM3输入捕获参数 TIM_IC_InitTypeDef sConfigIC; // 定义输入捕获结构体(IC:Input capture) sConfigIC.ICPolarity = TIM_ICPOLARITY_BOTHEDGE; // 上升沿下降沿都捕获 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 配置为TI1 sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频 sConfigIC.ICFilter = 0x03; // IC1F=0011 8个定时器时钟周期滤波 // 初始化TIM3 CH1通道,出错则进入错误处理函数 if (HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } HAL_TIM_Base_Start_IT(&htim3); // 使能更新中断(也就是TIM_IT_UPDATE) HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); // 开始捕获TIM3 CH1}</syntaxhighlight>使能GPIOC以及TIM3的时钟,并且配置PC6为TIM3的通道1。<syntaxhighlight lang="c++" line="1" start="96">void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim){ GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_TIM3_CLK_ENABLE(); // 使能TIM3时钟 __HAL_RCC_GPIOC_CLK_ENABLE(); // 开启GPIOC时钟 GPIO_Initure.Pin = GPIO_PIN_6; // PC6 GPIO_Initure.Mode = GPIO_MODE_AF_PP; // 推挽输出 GPIO_Initure.Pull = GPIO_PULLUP; // 上拉 GPIO_Initure.Speed = GPIO_SPEED_HIGH; // 高速模式 GPIO_Initure.Alternate = GPIO_AF2_TIM3; // PC6配置为TIM3通道1 HAL_GPIO_Init(GPIOC, &GPIO_Initure); HAL_NVIC_SetPriority(TIM3_IRQn, 10, 0); // 设置TIM3中断优先级 HAL_NVIC_EnableIRQ(TIM3_IRQn); // 使能TIM3中断}</syntaxhighlight>定时器周期中断回调函数,TIM3的自动装载值装满一次,进入一次此回调(本工程配置的参数是10ms进入一次)。
1.内部或者外部振荡器选择,也就是选择时钟信号的来源,是内部振荡,还是外部晶振。我们判断是否已经接收到引导码(根据引导码标志位判断),一旦接收到引导码,我们认为已经开始了一次NEC数据的接收。
2.时钟配置,选择系统、AHB总线及APB总线的时钟来源。如果是接收到引导码之后,第一次进入此函数,那么我们使能记录遥控器按键值的标志位,也就是代表接收到了一次NEC数据(不管数据对错)。
3.外设时钟配置,选择外设时钟来源。如果进入的次数少于11次,则继续增加计数,当计数值等于11时(也就是从接收到引导码已经过去至少110ms时),我们认为一次NEC的数据获取已经完成,此时清除周期回调的计数值,并且删除引导码标志位(下次再进到这个函数时,只有新的引导码数据到来,才会进行新的数据处理)。<syntaxhighlight lang="c++" line="1" start="122">void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ if(htim->Instance == TIM3) { if(irStatus & IR_STATUS_BootCode) // 如果接收到引导码 { irStatus &= ~IR_STATUS_Rising; // 删除上升沿标记(以防止本次出错,确保下次采集流程正确) if(tim3Cnt == 0) // 如果是第一次进入(计数为0) { irStatus |= IR_STATUS_BtnInfo; // 记录已经获取到IR按键信号(遥控器按键值) } if((tim3Cnt & 0X0F) < 11) // 进入回调少于11次 { tim3Cnt++; // 计数值自加 } else // 超过11次,代表一次采集超时(不论是否成功) { irStatus &= ~IR_STATUS_BootCode;// 删除引导码标记 tim3Cnt = 0; // 清除计数值 } } }}</syntaxhighlight>处理TIM3 CH1(PC6引脚)捕获的数据,这边要记得初始化中我们使能了上升沿下降沿都捕获数据。
为了给大家比较全面的展示各个时钟,我们振荡器选择HSI(内部16MHz高频)、HSE(外部8MHz高频)以及LSE(外部32.768KHz低频)三个。选择HSE作为PLL(锁相回路)时钟源,配置PLLCLK为80MHz。配置系统时钟SYSCLK、AHB高性能总线、APB外设总线(APB1及APB2)为80MHz。另外我们还分别配置了ADC、UART以及I2C的外设时钟。当有边沿捕获到来,我们判断此时PC6引脚的电平。
基础实验中的其他例程,大部分都是使用的相同的时钟配置函数,有特殊的时钟使用,将会在对应例程的源码详解中做针对性说明。 49 void SystemClock_Config(void) 50 { 51 RCC_OscInitTypeDef RCC_OscInitStruct; // 定义RCC内部/外部振荡器结构体 52 RCC_ClkInitTypeDef RCC_ClkInitStruct; // 定义RCC系统,AHB和APB总线时钟配置结构体 53 RCC_PeriphCLKInitTypeDef PeriphClkInit; // 定义RCC扩展时钟结构体 54 55 // 配置LSE驱动器功能为低驱动能力 56 __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW); 57 58 // 初始化CPU,AHB和APB总线时钟 59 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE 60 |RCC_OSCILLATORTYPE_LSE; // 设置需要配置的振荡器为HSI、HSE、LSE 61 // 配置HSE 62 RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 激活HSE时钟(开发板外部为8MHz) 63 // 配置LSE 64 RCC_OscInitStruct.LSEState = RCC_LSE_ON; // 激活LSE时钟(32.768KHz,低驱动) 65 // 配置HSI 66 RCC_OscInitStruct.HSIState = RCC_HSI_ON; // 激活HSI时钟 67 RCC_OscInitStruct.HSICalibrationValue = 16; // 配置HSI为16MHz 68 // 配置PLL 69 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开PLL 70 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 选择HSE时钟作为PLL入口时钟源,8MHz 71 RCC_OscInitStruct.PLL.PLLM = 1; // 配置PLL VCO输入分频为1,8/1 = 8MHz 72 RCC_OscInitStruct.PLL.PLLN = 20; // 配置PLL VCO输入倍增为20,8MHz*20 = 160MHz 73 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // SAI时钟7分频,160/7 = 22.857143MHz 74 RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // SDMMC、RNG、USB时钟2分频,160/2 = 80MHz 75 RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // 系统主时钟分区2分频,160/2 = 80MHz 76 // RCC时钟配置,出错则进入错误处理函数 77 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 78 { 79 _Error_Handler(__FILE__, __LINE__); 80 } 81 82 // 初始化CPU,AHB和APB总线时钟 83 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 84 |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; // 需要配置的时钟HCLK、SYSCLK、PCLK1、PCLK2 85 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 配置系统时钟为PLLCLK输入,80MHz 86 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟为系统时钟1分频,80/1 = 80MHz 87 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1时钟为系统时钟1分频,80/1 = 80MHz 88 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2时钟为系统时钟1分频,80/1 = 80MHz 89 // RCC时钟配置,出错则进入错误处理函数 90 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) // HCLK=80MHz,Vcore=3.3V,所以选择SW4(FLASH_LATENCY_4) 91 { 92 _Error_Handler(__FILE__, __LINE__); 93 } 94 95 // 初始化外设时钟 96 PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_USART2 97 |RCC_PERIPHCLK_LPUART1|RCC_PERIPHCLK_LPTIM1 98 |RCC_PERIPHCLK_I2C2|RCC_PERIPHCLK_ADC; // 需要初始化的外设时钟:USART1、USART2、LPUART1、LPTIM1、I2C2、ADC 99 PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; // 配置串口USART1时钟为PCLK2,80MHz 100 PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1; // 配置串口USART2时钟为PCLK1,80MHz 101 PeriphClkInit.Lpuart1ClockSelection = RCC_LPUART1CLKSOURCE_HSI; // 配置LPUART时钟为HSI,16MHz 102 PeriphClkInit.I2c2ClockSelection = RCC_I2C2CLKSOURCE_PCLK1; // 配置I2C2时钟为PCLK1,80MHz 103 PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE; // 配置LPTIM1时钟为LSE,32.768KHz 104 PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1; // 配置ADC时钟为PLLSAI1,现在为80MHz,下面会重新定义 105 PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE; // 配置PLLSAI1时钟为HSE,8MHz 106 PeriphClkInit.PLLSAI1.PLLSAI1M = 1; // 配置PLLSAI1分频为1 107 PeriphClkInit.PLLSAI1.PLLSAI1N = 8; // 配置PLLSAI1倍增为8 108 PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7; // SAI时钟7分频,64/7 = 9.142857MHz 109 PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2; // SDMMC、RNG、USB时钟2分频,64/2 = 32MHz 110 PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2; // 系统主时钟分区2分频,64/2 = 32MHz 111 PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_ADC1CLK; // 配置PLLSAI1输出为ADC1时钟,也就是配置ADC1时钟,32MHz 112 // 外设时钟配置,出错则进入错误处理函数 113 if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) 114 { 115 _Error_Handler(__FILE__, __LINE__); 116 } 117 118 // 配置内部主稳压器输出电压,配置为稳压器输出电压范围1模式,也就是:典型输出电压为1.2V,系统频率高达80MHz 119 if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) 120 { 121 _Error_Handler(__FILE__, __LINE__); 122 } 123 124 // 配置系统定时器中断时间,配置为HCLK的千分频 125 HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000); 126 127 // 配置系统定时器,配置为HCLK 128 HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); 129 130 // 系统定时器中断配置,设置系统定时器中断优先级最高(为0),且子优先级最高(为0) 131 HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); 132 }如果此时是高电平,则代表刚刚的是上升沿,此时我们使能上升沿标志位,并且清空定时器计数值。
如果此时是低电平,且上升沿标识位被置位,则我们根据获取到的计数值(也就是上一次高电平的持续时间)来判断本段PWM波代表的含义。<syntaxhighlight lang="c++" line="1" start="155">void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){ // 判断是否为TIM3 CH1捕获产生的回调 if((htim->Instance ==TIM3) && (htim->Channel = 8.5.4 gyu_i2c.= HAL_TIM_ACTIVE_CHANNEL_1)) { if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_6)) // 获取PC6引脚电平,如果是高电平,则代表是上升沿捕获 { __HAL_TIM_SET_COUNTER(&htim3, 0); // 清除TIM3定时器计数值 irStatus |= IR_STATUS_Rising; // 标记上升沿捕获 } else //如果是低电平,则代表是下降沿触发 { tim3Val = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1); // 读取TIM3 CH1定时器计数值 if(irStatus & IR_STATUS_Rising) // 存在上升沿标记,我们比较定时器计数值 { if(tim3Val > 260 && tim3Val < 860) // 高电平持续560us代表bit 0,范围560us±300us { irRecData <<= 1; // 左移一位 irRecData |= 0; // bit位赋值0 } else if(tim3Val > 1380 && tim3Val < 1980) // 高电平持续1680us代表bit 1,范围1680us±300us { irRecData <<= 1; // 左移一位 irRecData |= 1; // bit位赋值1 } else if(tim3Val > 2200 && tim3Val < 2800) // 高电平持续2500us代表本次按键结束,范围2500us±300us { irCnt++; // 按键次数新增1 tim3Cnt = 0; // 清除计数值 } else if(tim3Val > 4200 && tim3Val < 4800) // 高电平持续4500us代表新的按键,范围4500us±300us { irStatus |= IR_STATUS_BootCode; // 标记引导码 irCnt = 0; // 有新的按键到来,清除按键计数 } } irStatus &= ~IR_STATUS_Rising; // 清除上升沿标记 } }}</syntaxhighlight>轮询当前的按键信息。如果按键值标识存在,则代表有按键被按下,接着判断地址码以及数据数据正确,并将最终的按键数据添加到irinfo中留给应用层调用。<syntaxhighlight lang="c[编辑 | 编辑源代码] ++" line="1" start="206">irInfo_t IRBNT_POLL(void){ irInfo_t irinfo ={0,0}; // 定义按键信息结构体 uint8_t bcode, dcode; // 定义引导码正反编码 uint8_t bvalue, dvalue; // 定义按键值正反编码 if(irStatus & IR_STATUS_BtnInfo) // 如果获取到按键值 { bcode =irRecData >> 24; // 地址码在讲解i2c代码之前,我们先给大家讲解一下参数Timing,这个值是通过计算得来的,在STM32芯片手册的P1238页有计算公式说明,我们这边偷懒,利用STM32CUBE里面的配置功能,对应SHT20的I2C参数要求。 dcode = (irRecData >> 16) & 0xff; // 地址码反编码
我们配置I2C时钟为100KHz,Rise Time 300ns,Fall Time 100ns。最终得出Timing值为0x10D05E82。 if((bcode == (uint8_t)~dcode) && bcode == REMOTE_DEVICE_ID) // 判断地址码是否正确 { bvalue = irRecData >> 8; // 按键值 dvalue = irRecData; // 按键值反编码 if(bvalue == (uint8_t)~dvalue) // 判断按键值是否正确 { irinfo.irBtnVal = bvalue; // 将按键值赋给irinfo.irBtnVal } } irinfo.irBtnCnt = irCnt; // 将按键此处赋给irinfo.irBtnCnt }
初始化I2C引脚,选择的I2C2。 36 void MX_I2C2_Init(void) 37 { 38 hi2c2.Instance = I2C2; // I2C寄存器基础地址,定义为I2C2的 39 hi2c2.Init.Timing = 0x10D05E82return irinfo; // 指定I2C_TIMINGR寄存器值,此值必须在I2C初始化之前配置 40 41 if (HAL_I2C_Init(&hi2c2) != HAL_OK) // 初始化I2C2 42 {返回按键信息 43 _Error_Handler(__FILE__, __LINE__); // 如果初始化失败,则进入错误处理 44 } 45 }定义I2C2功能引脚,选择PB13为SCL引脚,PB14为SDA引脚。并且使能GPIOB以及I2C2的时钟。 55 void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle) 56 { 57 /</ 使能GPIOB引脚时钟,因为选择的I2C引脚均在PB上 58 __HAL_RCC_GPIOB_CLK_ENABLE(); 59 60 // 定义GPIO结构 61 GPIO_InitTypeDef GPIO_InitStruct; 62 63 // 判断I2C是否选择的是I2C2 64 if(i2cHandle-syntaxhighlight>Instance==I2C2) 65 { 66 // I2C2引脚配置 67 GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14; // 选择PB13为SCL引脚,PB14为SDA引脚 68 GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 外设功能为开漏模式 69 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉 70 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 高速模式 71 GPIO_InitStruct.Alternate = GPIO_AF4_I2C2; // 外设引脚选择I2C2 72 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始化IO配置 73 74 // 使能I2C2时钟 75 __HAL_RCC_I2C2_CLK_ENABLE(); 76 } 77 }I2C发送数据的函数。 89 uint8_t HAL_I2C_Send(uint8_t addr , uint8_t *pData, uint16_t len) 90 { 91 // 判断是否存在数据,不存在返回HAL_ERROR 92 if(len == 0 || pData == 0) 93 { 94 return HAL_ERROR; 95 } 96 97 // 发送数据,并返回发送状态 98 return HAL_I2C_Master_Transmit(&hi2c2,addr,pData,len,100); 99 }I2C接收数据的函数。 111 uint8_t HAL_I2C_Read(uint8_t addr, uint8_t *pData, uint16_t len) 112 { 113 // 判断是否存在数据,不存在返回HAL_ERROR 114 if(len == 0 || pData == 0) 115 { 116 return HAL_ERROR; 117 } 118 119 // 接收数据,并返回接收状态 120 return HAL_I2C_Master_Receive(&hi2c2,addr,pData,len,100); 121 }
==== 8.5.5 gyu_sht20.c[编辑 | 编辑源代码] ====读取SHT20温度的函数,最终返回是温度是采集值,不是真实的温度值。 52 uint16_t SHT20_ReadTemp(void) 53 { 54 uint16_t temp = 0; 55 56 // 发送“读取温度指令” 57 uint8_t cmd = SHT20_MEASURE_TEMP_CMD; 58 HAL_I2C_Send(SHT20_WRITE_ADDR,&cmd,1); 59 60 // 获取温度采集值,3位数据分别为:Data(MSB)、Data(LSB)、CheckSum 61 uint8_t pDATA[3] = {0,0,0}; 62 HAL_I2C_Read(SHT20_READ_ADDR,pDATA,3); 63 64 // 计算出真实的采集值,保留14bit(MSB 8bit、LSB 高6bit) 65 temp = pDATA[0]; 66 temp <<= 8; 67 temp += (pDATA[1] & 0xfc); 68 69 // 返回温度采集值 70 return temp; 71 }读取SHT20湿度的函数,最终返回是湿度是采集值,不是真实的湿度值。 81 uint16_t SHT20_ReadRH(void) 82 { 83 uint16_t rh = 0; 84 85 // 发送“读取湿度指令” 86 uint8_t cmd = SHT20_MEASURE_RH_CMD; 87 HAL_I2C_Send(SHT20_WRITE_ADDR,&cmd,1); 88 89 // 获取湿度采集值,3位数据分别为:Data(MSB)、Data(LSB)、CheckSum 90 uint8_t pDATA[3] = {0,0,0}; 91 HAL_I2C_Read(SHT20_READ_ADDR,pDATA,3); 92 93 // 计算出真实的采集值,保留12bit(MSB 8bit、LSB 高4bit) 94 rh = pDATA[0]; 95 rh <<= 8; 96 rh += (pDATA[1] & 0xf0); 97 98 // 返回湿度采集值 99 return rh; 100 }SHT20软件复位函数,工程中没有使用此函数。 void SHT20_SoftReset(void) { // 发送SHT20软件复位指令 uint8_t cmd = SHT20_MEASURE_RH_CMD; HAL_I2C_Send(SHT20_WRITE_ADDR,&cmd,1); }温湿度转换函数,用于将采集到的温湿度采集值,转化成真实的温湿度值。 127 float SHT20_Convert(uint16_t value,uint8_t isTemp) 128 { 129 float tmp = 0.0; 130 // 判断本次需要转换的值是温度还是湿度 131 if(isTemp) 132 { 133 tmp = -46.85 + (175.72* value)/(1 << 16); // 温度值转换,公式:T = -46.85 + 175.72*(S/2^16) 134 } 135 else 136 { 137 tmp = -6 + (125.0 *value)/(1<<16); // 湿度值转换,公式:RH = -6.00 + 125.00*(S/2^16) 138 } 139 return tmp; 140 }
[[分类:NB-IOT]]
[[分类:NBDK-L4]]
[[分类:教程]]
__强显目录__