510
个编辑
更改
→08-RGB实验
}
</syntaxhighlight>
== 8 10-串口打印实验[编辑 | 编辑源代码] ==
此实验给大家展示的是利用STM32L476的I2C外设功能,去获取sht20温湿度传感器采集的温湿度数据。并且将获取到的数据转换成真实的温湿度数据,格式化打印到串口显示。
=== 8.1 STM32L476 I2C简介[编辑 | 编辑源代码] ===
I2C(内部集成电路)总线接口处理STM32L4和串行I2C总线之间的通信。 它提供多主机功能,并控制所有I2C总线特定的排序,协议,仲裁和定时。 它支持标准模式(Sm),快速模式(Fm)和快速模式加(Fm +)。兼容SMBus(系统管理总线)和PMBus(电源管理总线)。
I2C主要功能:
•I2C总线规范rev03兼容性:
- 从模式和主模式
- 多主机功能
- 标准模式(最高100 kHz)
- 快速模式(最高400 kHz)
- 快速模式加(最高1 MHz)
- 7位和10位寻址模式
- 多个7位从机地址(2个地址,1个带可配置掩码)
- 所有7位地址确认模式
- 一般电话
- 可编程设置和保持时间
- 易于使用的事件管理
- 可选的时钟拉伸
- 软件重置
•具有DMA功能的1字节缓冲区
•可编程模拟和数字噪声滤波器
=== 8.2 硬件设计[编辑 | 编辑源代码] ===
选择STM32L4引脚PB13作为I2C SCL引脚,PB14作为I2C SDA引脚。
=== 8.3 实验准备[编辑 | 编辑源代码] ===
# 使用miniUSB线及10pin排线,通过Jlink仿真器连接PC端和开发板。
# 使用miniUSB线,连接PC与开发板USB接口。
# 将SW1拨到DBG端,SW2拨到MCU。
# 使用Keil打开基础实验 07-温湿度实验工程。
# 使用Xshell打开Jlink虚拟出的COM口
# 下载程序,并完成功能测试。
=== 8.4 实验验证[编辑 | 编辑源代码] ===
下载完成后,打开COM口,可以看到每隔500ms打印一次采集到的温湿度数据。
在采集的过程中,我们将手指按在SHT20上,可以看到温度和湿度都在上升,例如温度,由开始的20.4°C上升到最终的26.7°C。
=== 8.5 源码详解[编辑 | 编辑源代码] ===
本节中的源码说明,仅针对此例程中的重要功能,详细的源码介绍请大家参照代码后的注释。
==== 8.5.1 stm32l4xx_hal_conf.h[编辑 | 编辑源代码] ====
此文件位于“07-温湿度实验\Inc”路径中,主要用途是选择使能此例程使用到的库文件。
此例程我们主要给大家展示STM32L4的I2C功能,所以我们宏定义中打开I2C相关的。
103 // 使能的宏
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 // 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 // I2C
==== 8.5.2 main.c[编辑 | 编辑源代码] ====
main函数,我们的例程由此处开始执行,首先调用HAL_Init()函数初始化我们的模块,接着调用SystemClock_Config()函数初始化此例程用到的时钟,具体有哪些时钟被初始化,在gyu_util.c部分有详细说明。
接下来我们初始化了串口部分,目的是打印采集到的温湿度数据。
接下来初始化I2C引脚。
在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.5.3 gyu_util.c[编辑 | 编辑源代码] ====
时钟初始化函数,用于配置我们模块运行的系统时钟、AHB高性能总线时钟、APB外设总线时钟以及单个外设的时钟。
主要包含了三个部分的初始化配置。
1.内部或者外部振荡器选择,也就是选择时钟信号的来源,是内部振荡,还是外部晶振。
2.时钟配置,选择系统、AHB总线及APB总线的时钟来源。
3.外设时钟配置,选择外设时钟来源。
为了给大家比较全面的展示各个时钟,我们振荡器选择HSI(内部16MHz高频)、HSE(外部8MHz高频)以及LSE(外部32.768KHz低频)三个。选择HSE作为PLL(锁相回路)时钟源,配置PLLCLK为80MHz。配置系统时钟SYSCLK、AHB高性能总线、APB外设总线(APB1及APB2)为80MHz。另外我们还分别配置了ADC、UART以及I2C的外设时钟。
基础实验中的其他例程,大部分都是使用的相同的时钟配置函数,有特殊的时钟使用,将会在对应例程的源码详解中做针对性说明。
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 }
==== 8.5.4 gyu_i2c.c[编辑 | 编辑源代码] ====
在讲解i2c代码之前,我们先给大家讲解一下参数Timing,这个值是通过计算得来的,在STM32芯片手册的P1238页有计算公式说明,我们这边偷懒,利用STM32CUBE里面的配置功能,对应SHT20的I2C参数要求。
我们配置I2C时钟为100KHz,Rise Time 300ns,Fall Time 100ns。最终得出Timing值为0x10D05E82。
初始化I2C引脚,选择的I2C2。
36 void MX_I2C2_Init(void)
37 {
38 hi2c2.Instance = I2C2; // I2C寄存器基础地址,定义为I2C2的
39 hi2c2.Init.Timing = 0x10D05E82; // 指定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->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]]
[[分类:教程]]
__强显目录__