You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
9.7 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 1. ADC
## 1.1. ADC基本原理
什么是ADCAnalog-to-Digital Converter模拟信号转换为数字信号通过测量模拟信号的电压把电压值转换成数字进行后续的操作。
ADC的本质是通过测量电压值来获得被测信号的电压幅值并保存成一个数值。通过这种机制不仅可以测电压还可以测电阻、电容、电感、压力、温度等只要这些被测量通过一个传感器可以和电压之间产生某种函数关系。
对电压进行测量后保存到的有效数据的位数被叫做精度例如8位精度可以有256个值10位精度的有1024个值该MCU的ADC精度是12位的有4096个值
另外ADC中还有个重要的概念叫做参考电压也就是测量电压的基准。在该芯片中内部基准电压叫做 Vrefint经查阅资料应该是2.048V也就是说ADC最高能检测的电压是2048V。如果按照12位精度采样那得到的分辨率是0.0005V。
> 问题如果电压高于2.048V应该如何进行检测?
![image-20241013171356277](./img/image-20241013171356277.png)
以上是ADC的内部框图我们关注主要的是Vref、GPIO、ADC中断、DMA请求、ADCCLK。目前我们使用内部基准通过GPIO采集电压然后使用轮询模式读取电压值。其他方式还包括中断方式和DMA方式。
开发板内部有两个ADC的测试
![image-20241013171711301](./img/image-20241013171711301.png)
一个是温度热电偶,另外一个是电位器。具体使用我们在后面说明。
扩展阅读:
[ADC原理](https://www.bilibili.com/video/BV1BV4y1V7nE/?spm_id_from=333.337.search-card.all.click&vd_source=3c8e333d6657680a469ddf0238f01d6a)
## 1.2. PCM
PCM是一种通过波形幅值测量和对信号进行数字化的一种方案
![image-20241013172118023](./img/image-20241013172118023.png)
[PCM视频讲解](https://www.bilibili.com/video/BV1yb4y1M7a2/?spm_id_from=333.337.search-card.all.click&vd_source=3c8e333d6657680a469ddf0238f01d6a)
模拟信号通过间隔时间采样得到每个点的幅值然后进行保存和处理。计算机中常见的wav格式就是PCM采样后的文件。衡量PCM的指标有两个采样率和精度ADC精度。一般采样率越高越好信号的失真越小但是存储的空间越大。一般来说采样率不能低于最高频率的两倍因此以前常见的音频采样率是44k声音最高20k目前还有96k以上的高保真。
## 1.3. 实例NTC温度测试
任务描述通过NTC测量环境温度并通过串口打印出来。
NTC 又叫热敏电阻温度的变化会改变其电阻值。通过这一特性结合ADC就可以测量温度。
开发版的NTC是如下电路
![image-20241013183636425](./img/image-20241013183636425.png)
是接在PA4上面的一个电阻通过10K的R6形成分压等效电路如下
![alt text](img/ntc.drawio.png)
当NTC的电阻值变化的时候PA4这个分压点检测到的电压会随之变化。当对这个电压进行ADC转换后可以反向计算NTC的电阻值然后根据NTC的电阻和温度的关系可以计算出温度。
NTC是附件与开发板这样连接
![ad78bbf910c2747701476e350990a89](./img/ad78bbf910c2747701476e350990a89.jpg)
### 1.3.1. 配置
在引脚配置中选择PA4然后选择ADC1_IN4
![image-20241013185308417](./img/image-20241013185308417.png)
设置ADC参数
![image-20241013191944257](./img/image-20241013191944257.png)
Continuous Conversion Mode设为Enable使ADC转换持续进行不需要每次获取之前手动触发转换ADC_Regular_ConversionMode -> Rank -> Sampling Time设为239.5 Cycles最长采样时间可以获得更稳定的转换结果。
打开串口2
在菜单project Properties -> C/C++ Build -> Settings -> Tool Settings -> MCU Settings勾选Use float with printf ...
![image-20241013192823116](./img/image-20241013192823116.png)
这样printf就可以打印小数。
### 1.3.2. 代码编写
在 inc 目录新建一个头文件 NTC.h因为后面还会使用到NTC的一些功能
```h
#ifndef INC_NTC_H_
#define INC_NTC_H_
#include "main.h"
#include "math.h"
extern float NTC_Temperature;
float ADC2Resistance(uint32_t adc_value);
float resistance2Temperature(float R1);
#endif /* INC_NTC_H_ */
```
在 src 目录新建 NTC.c 文件这个文件是NTC转换成温度的一些函数。
```c
/**
* @brief 通过ADC值计算NTC电阻
*
* @param adc_value ADC原始值 [0, 4095]
* @retval 返回NTC电阻值浮点数类型单位Ω
*/
#include "NTC.h"
float NTC_Temperature;
float ADC2Resistance(uint32_t adc_value) {
return (adc_value / (4096.0f - adc_value)) * 10000.0f;
}
/**
* @brief 通过NTC电阻反推温度
*
* @param R1 NTC电阻值
* @retval 返回温度float类型单位摄氏度
*/
float resistance2Temperature(float R1) {
float B = 3950.0f;
float R2 = 10000.0f;
float T2 = 25.0f;
return (1.0 / ((1.0 / B) * log(R1 / R2) + (1.0 / (T2 + 273.15))) - 273.15);
}
```
以下是 main.c文件的内容
在 USER CODE BEGIN Includes 代码段引入头文件
```c
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
#include "NTC.h"
/* USER CODE END Includes */
```
在 USER CODE BEGIN 2 代码段加入如下内容:
```c
/* USER CODE BEGIN 2 */
char send_buf[50] = { 0 };
uint16_t adc_result = 0;
float ntc_resistance = 0.0f;
float temperature = 0.0f;
HAL_ADC_Start(&hadc1); // 开始连续ADC转换
HAL_Delay(500); // 等待ADC稳定
/* USER CODE END 2 */
```
完善主函数的循环:
```c
/* USER CODE BEGIN WHILE */
while (1) {
adc_result = HAL_ADC_GetValue(&hadc1); // 获取ADC值
ntc_resistance = ADC2Resistance(adc_result); // 获取ADC值
temperature = resistance2Temperature(ntc_resistance); // 获取ADC值
sprintf(send_buf, "阻值%.1f Ω,温度: %.2f ℃\r\n", ntc_resistance,
temperature); // 将变量打印为字符串
HAL_UART_Transmit(&huart2, (uint8_t*) send_buf, strlen(send_buf), 10); // 通过串口2发送
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
```
### 1.3.3. 代码分析
# 2. I2C
[I2C 温湿度传感器](https://www.bilibili.com/video/BV1QN411D7ak/?spm_id_from=333.788&vd_source=3c8e333d6657680a469ddf0238f01d6a)
## 2.1. 设置
1. 先新建一个工程,取名为 humidity
2. 设置调试模式为 Serial Wire
3. 打开串口2
4. 查看开发板19号外设使用PB6/PB7
![image-20241015151124859](./img/image-20241015151124859.png)
在引脚设置中点击PB6看到使用的是I2C1
![image-20241015151052757](./img/image-20241015151052757.png)
5. 在Connectivity中找到I2C1设置I2C模式为 I2C
![image-20241015151355494](./img/image-20241015151355494.png)
6. 在Project Manager中设置为每个外设使用独立的 c/.h 文件:
![image-20241015151501806](./img/image-20241015151501806.png)
7. 按照上个例子设置printf使用浮点
## 2.2. 编码
inc目录建立一个新文件aht20.h
```h
#ifndef __DHT20_H__
#define __DHT20_H__
#include "i2c.h"
// 初始化AHT20
void AHT20_Init(void);
// 获取温度和湿度
void AHT20_Read();
#endif
```
src 目录建立一个新文件 aht20.c
```c
#include <aht20.h>
#define AHT20_ADDRESS 0x70
/**
* 查看状态字是否校准
*/
void AHT20_Init(void) {
uint8_t read_buf = 0;
// 等待DHT20上电稳定
HAL_Delay(40);
HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, &read_buf, 1, 10);
if ((read_buf & 0x08) != 0x08) {
// 状态字没有校准,发送出初始化命令
uint8_t send_buf[3] = { 0xBE, 0x08, 0x00 };
HAL_I2C_Master_Transmit_DMA(&hi2c1, AHT20_ADDRESS, send_buf, 3);
HAL_Delay(100);
}
}
/**
*
*/
void AHT20_Read(float *temp, float *hum) {
uint8_t sendBuffer[3] = { 0xAC, 0x33, 0x00 };
uint8_t readBuffer[6];
HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, sendBuffer, 3, HAL_MAX_DELAY);
HAL_Delay(75);
HAL_I2C_Master_Receive(& hi2c1, AHT20_ADDRESS, readBuffer, 6, HAL_MAX_DELAY);
if ((readBuffer[0] & 0x80) == 0x00) {
uint32_t data = 0;
data = ((uint32_t) readBuffer[3] >> 4) + ((uint32_t) readBuffer[2] << 4) + ((uint32_t) readBuffer[1] << 12);
*hum = data * 100.0f / (1 << 20);
data = (((uint32_t) readBuffer[3] & 0x0F) << 16) + (((uint32_t) readBuffer[4]) << 8) + (uint32_t) readBuffer[5];
*temp = data * 200.0f / (1 << 20) - 50;
}
}
```
main.c 文件的 USER CODE BEGIN Includes 区域添加头文件:
```c
/* USER CODE BEGIN Includes */
#include "aht20.h"
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
```
main函数的 USER CODE BEGIN 2 区域添加内容
```c
/* USER CODE BEGIN 2 */
AHT20_Init();
float temp, hum;
char message[50];
/* USER CODE END 2 */
```
完善 main 函数的 while 循环:
```c
while (1) {
AHT20_Read(&temp, &hum);
sprintf(message, "温度:%.1f 湿度:%.1f \n", temp, hum);
HAL_UART_Transmit(&huart2, (uint8_t*) message, strlen(message), 100);
HAL_Delay(2000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
```
## 2.3. 代码分析
### 2.3.1. I2C的地址
在启动传输后,随后传输的I2C首字节包括7位的I2C设备地址 0x38和一个SDA方向位 x(读R:1写W:0)。
![image-20241015145204114](./img/image-20241015145204114.png)
因此,如果是读:实际地址是 0x71如果是写实际地址是0x70注意代码中关于I2C的读写命令
HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive 的地址都使用0x70HAL_I2C_Master_Transmit会自动设置最后一位是0仍然是0x70HAL_I2C_Master_Receive会自动设置最后一位是1就是0x71
### 温湿度数据的重组
按照AHT20的说明p8返回六个字节的顺序
![image-20241015162601769](./img/image-20241015162601769.png)
![image-20241015162613515](./img/image-20241015162613515.png)
![alt text](img/aht20_data.drawio.png)