《STM32单片机开发》p7

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的三种节电模式

  1. 睡眠模式:CPU时钟关, 对其他时钟和 ADC 时 钟 无 影 响
  2. 停止模式:所有使用1.8V 的区域的时钟 都已关闭,HSI 和HSE的振荡 器关闭(内部寄存器保持,SRAM保持,可以继续执行)
  3. 待机模式:所有使用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结构单元由三个基本部分组成:

  1. T - Type (类型)

    • 作用: 标识这个数据单元所代表的"含义"或"类型"。接收方根据Type来知道后面的Value部分存储的是什么类型的数据(例如,是一个用户名、一个年龄、还是一个IP地址)。
    • 长度: 通常是1个、2个或4个字节。这个长度在某个协议或规范中是固定的。
  2. L - Length (长度)

    • 作用: 明确指定后面Value字段的字节长度。
    • 好处: 接收方无需依赖任何分隔符或猜测,就能准确地知道应该读取多少字节来获取完整的数据。这解决了粘包/半包问题。
    • 长度: 通常是1个、2个或4个字节,定义了Length字段本身能表示的最大Value长度。
  3. V - Value (值)

    • 作用: 实际要传输的数据内容本身。
    • 长度: 由前面的Length字段明确指定,可以是0到很大(取决于Length字段的字节数)的任意长度。

JSON数据格式

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它采用完全独立于编程语言的文本格式来存储和表示数据,但其结构清晰、易于人阅读和编写,同时也易于机器解析和生成。

核心概念

JSON 的核心理念是构建两种结构,这两种结构在绝大多数编程语言中都有对应的数据类型:

  1. "键/值"对的集合:在各种语言中,它被实现为对象、记录、结构、字典、哈希表、有键列表或关联数组。
  2. 值的有序列表:在大多数语言中,它被实现为数组、向量、列表或序列。

原始数据

复制代码
{
    '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>
相关推荐
田甲2 小时前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
FakeOccupational2 小时前
电路笔记(信号):网线能传多少米?网线信号传输距离
开发语言·笔记·php
up向上up2 小时前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
Yawesh_best11 小时前
告别系统壁垒!WSL+cpolar 让跨平台开发效率翻倍
运维·服务器·数据库·笔记·web安全
纳祥科技11 小时前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
单片机日志13 小时前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息
松涛和鸣14 小时前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法
Ccjf酷儿14 小时前
操作系统 蒋炎岩 3.硬件视角的操作系统
笔记
习习.y14 小时前
python笔记梳理以及一些题目整理
开发语言·笔记·python