SHT30
- SHT30 的IIC地址 0x44 或 0x45(ADDR管脚处于高电平时)
- SHT30 设置为周期采样的命令 0x2737
- SHT获取数据的指令 0xE000
实现代码:
sht3x.h
#ifndef __SHT3X_H
#define __SHT3X_H
#include <stm32f10x_conf.h>
// SHT3x 的IIC地址
#define SHT_ADDR (0x44 << 1)
#define PERIODIC_MODE_CMD (0x2737) // 设置为周期模式的命令
#define FETCH_DATA_CMD (0xE000) // 获取数据的命令
// 初始化
void sht_init(void);
// 将SHT3x 设置为周期采样模式
void sht_set_periodic_mode(void);
// 读取 温度sht_data[0]和湿度sht_data[1],成功返回1,失败返回0
int sht_read_data(float sht_data[2]);
#endif // __SHT3X_H
sht3x.c
#include <stdio.h>
#include "systick.h"
#include "gpio_iic.h"
#include "sht3x.h"
// 初始化
void sht_init(void)
{
iic_init();
}
// 将SHT3x 设置为周期采样模式
void sht_set_periodic_mode(void)
{
iic_start();
iic_send_byte(SHT_ADDR); // 写
if(iic_wait_ack()) {
printf("SHT3x module not found!\r\n");
iic_stop();
return;
}
iic_send_byte(PERIODIC_MODE_CMD >> 8);
iic_wait_ack();
iic_send_byte(PERIODIC_MODE_CMD & 0xFF);
iic_wait_ack();
iic_stop();
delay_ms(5); // 等待设置成功
}
// 读取 温度sht_data[0]和湿度sht_data[1],成功返回1,失败返回0
int sht_read_data(float sht_data[2])
{
int i;
u8 buf[6] = {0}; // 用于存接收到的6字节数据
u16 temp = 0; // 温度
u16 hum = 0; // 湿度
iic_start();
iic_send_byte(SHT_ADDR); // 写
if(iic_wait_ack()) {
printf("SHT3x module not found!\r\n");
iic_stop();
return 0;
}
// 发送取数据的指令。
iic_send_byte(FETCH_DATA_CMD >> 8);
iic_wait_ack();
iic_send_byte(FETCH_DATA_CMD & 0xFF);
iic_wait_ack();
// 发读指令
iic_start();
iic_send_byte(SHT_ADDR | 0x01); // 读
if (iic_wait_ack()) {
printf("SHT Read Error\r\n");
iic_stop();
return 0;
}
for (i = 0; i < 6; i++) {
if (i == 5) { // 最后一次接收回复 NACK
buf[i] = iic_recv_byte(0);
} else {
buf[i] = iic_recv_byte(1);
}
}
iic_stop();
// 开始计算温度和湿度数据
temp = (((u16)buf[0]) << 8) | buf[1];
hum = (((u16)buf[3]) << 8) | buf[4];
sht_data[0] = -45 + 175.0 * temp / 65535;
sht_data[1] = 100.0 * hum / 65535;
return 1;
}
STM32F103 的 IIC 控制器
特点
-
传输速率
- 标准模式100kbps
- 快速模式400kbps
-
传输模式
- 主机模式
- 从机模式
eeprome页写操作图示:

嵌入式实时操作系统
-
开源
- μC/OS-II、μC/OS-III(开源
- freeRTOS
-
非开源
- VxWorks
- QNX
iic.c
#include "systick.h"
#include "bitband.h"
#include "gpio_iic.h"
// 初始化 IIC 的IO 端口:SDA:PB7,SCL:PB6
void iic_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;
// 使能 GPIOB 和 I2C1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// 初始化 PB6/PB7 为复用开漏输出
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; //复用开漏输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6| GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 初始化I2C控制器
// 设置工作模式为标准的I2C模式
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
// 自己作为从机的地址
I2C_InitStruct.I2C_OwnAddress1 = 0x7E;
// 是能应当,再接收数据后自动发送应答信号。
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
// 设置地址识别模式为7bit模式
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
// 设置 I2C 的通信速率为400KHz(快速模式)
I2C_InitStruct.I2C_ClockSpeed = 400000;
// 设置占空比:高电平占 33% 低电平66%
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Init(I2C1, &I2C_InitStruct); // 初始化各个寄存器。
I2C_Cmd(I2C1, ENABLE); // 发送使能命令
}
// 主机发送起始信号
void iic_start(void)
{
// 产生起始信号(条件)
I2C_GenerateSTART(I2C1, ENABLE);
// 等待EV5事件(起始条件已经发送)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
}
//主机发送停止信号
void iic_stop(void)
{
// 产生停止信号(条件)
I2C_GenerateSTOP(I2C1, ENABLE);
// 等待释放总线
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF));
}
// 发送从机地址 addr, rw=0为写,rw=1为读
void iic_send_addr(u8 addr, int rw)
{
if (rw) { // 读模式
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
// 等待 EV6
while(I2C_CheckEvent(
I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
} else {
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(I2C_CheckEvent(
I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
}
}
// 主机发送一个字节
void iic_send_byte(u8 txd)
{
I2C_SendData(I2C1, txd);
// 等待事件8
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));
}
// 主机接收一个字节并回复ack,参数ack:1表示应答,0表示非应答。
u8 iic_recv_byte(u8 ack)
{
u8 receive = 0;
// 配置应答寄存器,本次接收回复那种应答。
if (ack) {
iic_send_ack(); // 设置为应答
} else {
iic_send_nack(); // 设置为非应当
}
// 等待EV7事件(此时已经接收到数据)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
// 读取接收到的数据
receive = I2C_ReceiveData(I2C1);
return receive;
}
// 主机等待从机,如果从机未响应(收到NACK),则发送停止
// 信号结束本次通信,非应答返回1(NACK),应答返回0,ACK);
u8 iic_wait_ack(void)
{
uint32_t timeout = 100000;
// 等待EV6事件
while(timeout--) {
if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) ||
I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) {
return 0; // 收到 ACK
}
}
iic_stop(); // 超时,发送停止信号
return 1;
}
// 主机发送 ACK
void iic_send_ack(void)
{
I2C_AcknowledgeConfig(I2C1, ENABLE);
}
// 主机发送 NACK
void iic_send_nack(void)
{
I2C_AcknowledgeConfig(I2C1, DISABLE);
}
STM32的三种节电模式
- 睡眠模式:CPU时钟关, 对其他时钟和 ADC 时 钟 无 影 响
- 停止模式:所有使用1.8V 的区域的时钟 都已关闭,HSI 和HSE的振荡 器关闭(内部寄存器保持,SRAM保持,可以继续执行)
- 待机模式:所有使用1.8V 的区域的时钟 都已关闭,HSI 和HSE的振荡 器关闭(全部断电)
RTC 模块
rtc.h
#ifndef __RTC_H
#define __RTC_H
#include <stm32f10x_conf.h>
typedef struct {
uint16_t tm_year; // 年
uint8_t tm_month; // 月
uint8_t tm_day; // 日
uint8_t tm_hour; // 时
uint8_t tm_min; // 分
uint8_t tm_sec; // 秒
uint8_t tm_wday; // 周几,取值范围[0~6],周一为0
uint8_t tm_yday; // 一年中的第几天(1~366);
} time_struct_t;
// 第一次上电时,RTC时钟运行第一次初始化,成功返回1
void rtc_first_run_init(void);
// RTC 初始化
void rtc_init(void);
// 设置 RTC计数器的值(1970-1-1 0:0:0 UTC)至今的秒数
void rtc_set_clock_time(uint32_t utc_second);
// 获取RTC的计数器的值
uint32_t rtc_get_clock_time(void);
// 设置 RTC闹钟的计数值。
void rtc_set_alarm_time(uint32_t utc_second);
// 打开闹钟中断
void rtc_enable_alarm(void);
// 关闭闹钟中断
void rtc_disable_alarm(void);
// 以下时软件计算 本地时间和 UTC秒数
// 判断year 是否为闰年
int is_leap_year(uint16_t year);
// 将 UTC 秒数转为本地时间
void UTC_second_to_localtime(uint32_t utc_second,
time_struct_t * time_struct);
// 将 本地时间 转为UTC 秒数,并返回
uint32_t localtime_to_UTC_second(time_struct_t * time_struct);
#endif // __RTC_H
rtc.c
#include <stdio.h>
#include "systick.h"
#include "rtc.h"
// 定义时区
static uint8_t time_zone = 8; // 东8区
//定义平年/闰年月份日期表(根据平/闰年修改2月天数)
static uint8_t month_table[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
// 第一次上电时,RTC时钟运行第一次初始化,成功返回1
void rtc_first_run_init(void)
{
// 使能电源和后备域时钟源
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
// 使能后备域访问
PWR_BackupAccessCmd(ENABLE);
// 复位后备域区域
BKP_DeInit();
// 开启外部时钟源 LSE
RCC_LSEConfig(RCC_LSE_ON);
// 等待外部时钟源准备完毕
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
// 开启LSE ,选择 RTC 时钟源,使能RTC时钟
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
// 使能RTC
RCC_RTCCLKCmd(ENABLE);
// 等待RTC寄存器同步
RTC_WaitForSynchro();
// 初始化 RTC,设置预分频系数(实际会加1)
RTC_WaitForLastTask(); // 等待RTC_CRL 寄存器的 RTOFF 位置"1"
RTC_SetPrescaler(32767);
RTC_WaitForLastTask();
// 设置RTC的初始日期和时间
RTC_SetCounter(1672502400); // 设置初始值 2023-1-1 0:0:0
RTC_WaitForLastTask();
printf("first init RTC!\r\n");
}
// RTC 初始化
void rtc_init(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
uint16_t bkp_dr6_value;
// 开启PWR 和 BKP 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
// 使能后备域
PWR_BackupAccessCmd(ENABLE);
// 读取后备域寄存器 DR6的值,看是不是 0x6668
bkp_dr6_value = BKP_ReadBackupRegister(BKP_DR6);
if (bkp_dr6_value != 0x6668) { // 第一次运行
rtc_first_run_init(); // 进入RTC首次初始化
BKP_WriteBackupRegister(BKP_DR6, 0x6668);
// 验证是否写入成功
bkp_dr6_value = BKP_ReadBackupRegister(BKP_DR6);
if (bkp_dr6_value != 0x6668)
printf("Write Backup Value Error!");
} else {
// 如果没有进入首次初始化,则需要等待RTC_CRL 寄存器的 RSF 置1
RTC_WaitForSynchro();
}
// 使能RTC秒中断
RTC_ITConfig(RTC_IT_SEC, ENABLE); // 访问后备域,需要后备域时钟。
// 使能RTC闹钟中断
RTC_ITConfig(RTC_IT_ALR, ENABLE);
// 关闭后备域的访问,为了安全。
PWR_BackupAccessCmd(DISABLE);
// 设置完成 RTC计数器后关闭时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, DISABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, DISABLE);
// 初始化 NVIC
NVIC_InitStruct.NVIC_IRQChannel = RTC_IRQn; // 中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
// RTC中断处理函数
void RTC_IRQHandler(void)
{
// 在中断中应当用 RTC_GetITStatus 来获取中断状态标记
if (RTC_GetITStatus(RTC_IT_SEC) == SET) {
printf("second tick is set!\r\n");
RTC_ClearITPendingBit(RTC_IT_SEC);
} else if (RTC_GetITStatus(RTC_IT_ALR) == SET) {
printf("Alarm tick is set!\r\n");
RTC_ClearITPendingBit(RTC_IT_ALR);
}
}
// 设置 RTC计数器的值(1970-1-1 0:0:0 UTC)至今的秒数
void rtc_set_clock_time(uint32_t utc_second)
{
// 开启PWR 和 BKP 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 使能RTC和后备域寄存器的访问
RTC_WaitForLastTask();
RTC_SetCounter(utc_second);
RTC_WaitForLastTask();
PWR_BackupAccessCmd(DISABLE); // 禁止访问后备域寄存器。
RTC_WaitForLastTask();
// 设置完成 RTC计数器后关闭时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, DISABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, DISABLE);
}
// 获取RTC的计数器的值
uint32_t rtc_get_clock_time(void)
{
// 获取 RTC计数器的值
return RTC_GetCounter();
}
// 设置 RTC闹钟的计数值。
void rtc_set_alarm_time(uint32_t utc_second)
{
// 开启PWR 和 BKP 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 使能RTC和后备域寄存器的访问
RTC_WaitForLastTask(); // 等待上一次任务完成
RTC_SetAlarm(utc_second); // 设置闹钟寄存器的值。
RTC_WaitForLastTask(); // 等待上一次任务完成
PWR_BackupAccessCmd(DISABLE); // 禁止访问后备域寄存器。
RTC_WaitForLastTask();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, DISABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, DISABLE);
}
// 打开闹钟中断
void rtc_enable_alarm(void)
{
RTC_ITConfig(RTC_IT_ALR, ENABLE); // 使能 RTC_IT_ALR中断
}
// 关闭闹钟中断
void rtc_disable_alarm(void)
{
rtc_set_alarm_time(0); // 将闹钟寄存器的值置零。
RTC_ITConfig(RTC_IT_ALR, DISABLE); // 关闭 RTC_IT_ALR中断
}
// 以下时软件计算 本地时间和 UTC秒数
// 判断year 是否为闰年
int is_leap_year(uint16_t year)
{
// 能被 400整除 或 能被 4整除但不能被 100整除。
return ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0));
}
// 将 UTC 秒数转为本地时间
/* 算法:
将utc_second 换算成本地的秒数,再换算成距离1970年1月1日的天数
使用循环从 1970年1月开始逐月减掉每个月的天数。最终如果天数不足
本月时长,则时间就落在当前月的某一天。
*/
void UTC_second_to_localtime(uint32_t utc_second,
time_struct_t * time_struct)
{
// 将 UTC 的秒数换算成本地的秒数。
uint32_t local_second = utc_second + time_zone * (60*60);
uint32_t local_days = local_second / (24*60*60); // 计算总的天数
uint16_t local_year; // 用于定位本地的年。
uint8_t local_month; // 用于定位本地的月。
int index;
// 根据1970-1-1是星期四(用3表示)计算当前是星期几
time_struct->tm_wday = (local_days + 3) % 7;
// 计算本地时分秒
time_struct->tm_hour = local_second / 3600 % 24;
time_struct->tm_min = local_second / 60 % 60;
time_struct->tm_sec = local_second % 60;
// 计算年月日,
for (local_year = 1970; ; local_year++) {
// 如果 当前年份为 闰年,则将 2 月改为 29天,否则改为 28天
if (is_leap_year(local_year))
month_table[1] = 29;
else
month_table[1] = 28;
//更新一年中的第几天(每次循环都刷新一下)
time_struct->tm_yday = local_days + 1;
// 查找月份
for (local_month = 1; local_month <= 12; local_month++) {
index = local_month - 1; // 计算月份所在的索引位置
// 如果 local_days 小于本月的天数,再时间落在了本月
if (local_days < month_table[index]) {
time_struct->tm_year = local_year; // 年
time_struct->tm_month = local_month; // 月
time_struct->tm_day = local_days + 1; // 日
return; // 计算完毕
}
// 不在本月,减掉本月天数,再看是否落在下一个月。
local_days -= month_table[index];
}
}
}
// 将 本地时间 转为UTC 秒数,并返回
uint32_t localtime_to_UTC_second(time_struct_t * time_struct)
{
uint32_t local_second = 0; // 用于保存本地的秒数
uint16_t local_year;
uint8_t local_month;
int index;
// 计算 1970 年 ~ time_struct->tm_year - 1 的秒数
for (local_year = 1970; local_year < time_struct->tm_year;
local_year++) {
if (is_leap_year(local_year))
local_second += 366 * 24 * 60 * 60;
else
local_second += 365 * 24 * 60 * 60;
}
// 计算当前年前 n 个月的秒数(1月 ~ time_struct->tm_month-1)
if (is_leap_year(time_struct->tm_year))
month_table[1] = 29;
else
month_table[1] = 28;
for (local_month = 1; local_month < time_struct->tm_month;
local_month++){
index = local_month - 1; // 计算当前月的索引
local_second += month_table[index] * 24 * 60 * 60;
}
// 计算当月 time_strcut->tm_day 的天数对应的秒数
local_second += (time_struct->tm_day-1) * 24 * 60 * 60;
// 计算 时分秒对应的秒数
local_second += time_struct->tm_hour * 60 * 60;
local_second += time_struct->tm_min * 60;
local_second += time_struct->tm_sec;
// 返回去掉时区影响后的UTC秒数。
return local_second - time_zone * 60 * 60;
}
rtc测试程序main.c
#include <stdio.h>
#include <string.h>
#include "led.h"
#include "beep.h"
#include "button.h"
#include "systick.h"
// #include "led_digital_tube.h"
#include "dht11.h"
#include "adc.h"
#include "usart1.h"
#include "eeprom.h"
#include "oled.h"
#include "wfont.h"
#include "sht3x.h"
#include "power_manager.h"
#include "rtc.h"
int main(void) {
// NVIC优先级分组, 2bit 用于表示抢断优先级,2bit为子优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
led_init(); // 初始化LED
beep_init(); // 初始化 蜂鸣器
button_init();
systick_init(); // 初始化计时器
usart1_init(115200);
eeprom_init();
oled_init(); // 初始化 OLED
dht11_init();
adc1_init();
rtc_init();
sht_init();
sht_set_periodic_mode();
printf("RTC Demo\r\n");
delay_ms(1000);
// oled_draw_picture(elephant128x64);
// oled_fill_rect(10, 10, 118, 54);
// oled_draw_rect(10, 10, 118, 54);
// oled_fill(0x05);
// oled_clear();
oled_draw_rect(0, 0, 127, 63);
// oled_draw_text8(0, 0, "hello world");
// oled_draw_text16(0, 16, "\xce\xc2\xb6\xc8: 36.5\xa1\xe6");
oled_refresh_framebuffer(); // 将缓冲区的内容刷新到屏幕
while(1) {
char buf[30];
u8 value[5];
float sht_value[2]; // 用于保存sht数据
uint32_t utc_second = 0;
time_struct_t local_time;
// dht11_get_values(value);
sht_read_data(sht_value);
sprintf(buf, "\xce\xc2\xb6\xc8: %5.1f\xa1\xe6", sht_value[0]);
oled_draw_text16(8, 8, buf); // 显示温度
sprintf(buf, "\xca\xaa\xb6\xc8: %5.1f%%", sht_value[1]);
oled_draw_text16(8, 24, buf); // 显示湿度
// 显示ADC的值(通道10)
// sprintf(buf, "ADC Value: %04d", adc1_get_value(10));
// oled_draw_text8(8, 40, buf);
// 显示时间
utc_second = rtc_get_clock_time();
UTC_second_to_localtime(utc_second, &local_time);
sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d",
local_time.tm_year, local_time.tm_month,
local_time.tm_day, local_time.tm_hour,
local_time.tm_min, local_time.tm_sec);
oled_draw_text8(8, 40, buf);
// 显示LED 0 灯的状态
led_set_status(0, !led_get_status(0));
sprintf(buf, "LED(0):%3s", led_get_status(0)? "ON" : "OFF");
oled_draw_text8(8, 48, buf);
oled_refresh_framebuffer();
delay_ms(500);
if (button_status(2)) {
// 关闭所有的外设
oled_display_off();
pwr_enter_standby_mode();
}
// 如果按下 Key0,则进入RTC管理功能,并在串口中进行设置。
if (button_status(0)) {
int sel = 0;
printf("1) set a alarm after 10 second later!\r\n");
printf("2) display current datetime!\r\n");
printf("3) set datetime to 2025-11-3 14:23:01!\r\n");
printf("please select:");
scanf("%d", &sel);
switch (sel) {
case 1:
utc_second = rtc_get_clock_time();
utc_second += 10;
rtc_set_alarm_time(utc_second);
printf("Alarm Set AT UTC Second:%d\r\n", utc_second);
break;
case 2:
utc_second = rtc_get_clock_time();
UTC_second_to_localtime(utc_second, &local_time);
printf("%4d-%02d-%02d %02d:%02d:%02d\r\n",
local_time.tm_year, local_time.tm_month,
local_time.tm_day, local_time.tm_hour,
local_time.tm_min, local_time.tm_sec);
break;
case 3:
local_time.tm_year = 2025;
local_time.tm_month = 11;
local_time.tm_day = 3;
local_time.tm_hour = 14;
local_time.tm_min = 23;
local_time.tm_sec = 1;
utc_second = localtime_to_UTC_second(&local_time);
rtc_set_clock_time(utc_second);
default:
break;
}
}
}
}
常用数据传输格式
TLV格式
TLV,即 Type-Length-Value,是一种高效、灵活、可扩展的数据编码格式。它被广泛应用于通信协议、数据存储和序列化中,用以组织和传输结构化的数据。
顾名思义,一个TLV结构单元由三个基本部分组成:
-
T - Type (类型)
- 作用: 标识这个数据单元所代表的"含义"或"类型"。接收方根据Type来知道后面的Value部分存储的是什么类型的数据(例如,是一个用户名、一个年龄、还是一个IP地址)。
- 长度: 通常是1个、2个或4个字节。这个长度在某个协议或规范中是固定的。
-
L - Length (长度)
- 作用: 明确指定后面Value字段的字节长度。
- 好处: 接收方无需依赖任何分隔符或猜测,就能准确地知道应该读取多少字节来获取完整的数据。这解决了粘包/半包问题。
- 长度: 通常是1个、2个或4个字节,定义了Length字段本身能表示的最大Value长度。
-
V - Value (值)
- 作用: 实际要传输的数据内容本身。
- 长度: 由前面的Length字段明确指定,可以是0到很大(取决于Length字段的字节数)的任意长度。
JSON数据格式
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它采用完全独立于编程语言的文本格式来存储和表示数据,但其结构清晰、易于人阅读和编写,同时也易于机器解析和生成。
核心概念
JSON 的核心理念是构建两种结构,这两种结构在绝大多数编程语言中都有对应的数据类型:
- "键/值"对的集合:在各种语言中,它被实现为对象、记录、结构、字典、哈希表、有键列表或关联数组。
- 值的有序列表:在大多数语言中,它被实现为数组、向量、列表或序列。
原始数据
{
'name': 'weimingze',
'age': 35,
'scores': [100, 60, 80],
'friend': ['张三', '李四']
}
json 格式:
{"name": "weimingze", "age": 35, "scores": [100, 60, 80], "friend": ["\u5f20\u4e09", "\u674e\u56db"]}
格式化后:
{
"name": "weimingze",
"age": 35,
"scores": [100, 60, 80],
"friend": ["\u5f20\u4e09", "\u674e\u56db"]
}
XML 数据格式
XML (Extensible Markup Language)可扩展标记语言
XML是一种非常重要且广泛使用的数据表示格式,它的核心思想是用一种结构化的、纯文本的形式来存储和传输数据。
<data><name>weimingze</name><age>35</age><scores><item>100</item><item>60</item><item>80</item></scores><friend><item>张三</item><item>李四</item></friend></data>
格式化以后
<data>
<name>weimingze</name>
<age>35</age>
<scores>
<item>100</item>
<item>60</item>
<item>80</item>
</scores>
<friend>
<item>张三</item>
<item>李四</item>
</friend>
</data>