51单片机学习笔记-10IIC总线

news2024/11/16 16:25:48

10 I2C总线

[toc]

注:笔记主要参考B站江科大自化协教学视频“51单片机入门教程-2020版 程序全程纯手打 从零开始入门”。


10.1 AT24C02和I2C介绍

10.1.1 存储器介绍

图10-1 存储器分类

一般来说,RAM读写速度极快,但掉电丢失;而ROM相对来说读写速度慢,但是可以长期存储数据。

从实现功能的角度来说,只需要关注“地址总线”和“数据总线”即可。为了使主机和从机完成对这些数据的交互,便渐渐的演变出一些实用的特定格式的通信方式,但这些通信方式本身最重要的都是控制“地址”和“数据”的读写。

10.1.2 AT24C02介绍
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。

存储介质:E2PROM。
通讯接口:I2C总线。
容量:256字节,即地址位宽位16,范围是0~255。

图10-2 AT24C02实物图

根据下面芯片的引脚,只需要记住此开发板上EEPROM的“器件地址”为000,其余的只需要考虑SCL和SDA两根线的通信细节即可。

图10-3 AT24C02原理图及功能

10.1.3 I2C总线介绍
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线,只需要两根通信线:SCL(Serial Clock)、SDA(Serial Data)便可以实现同步、半双工通信。通信过程中包含数据应答位ACK,进一步提高了通信的可靠性。通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性;对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度。

图10-4 使用I2C总线的器件举例

图10-5给出了I2C多机通信的示意图。可以看出:

  • 所有I2C设备的SCL连在一起,SDA连在一起。
  • 设备的SCL和SDA均要配置成开漏输出模式(高电平时引脚悬空,无上拉电阻)。
  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右。
图10-5 I2C多机通信示意图

之所以这样设计,是因为开漏输出和上拉电阻的共同作用实现了“线与”的功能,可以解决多机通信互相干扰的问题。

10.1.4 AT24C02的数据帧
下面给出I2C数据帧中需要关注的6个关键问题:

  • 起始条件:SCL高电平期间,SDA从高电平切换到低电平。
  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平。
  • 发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
  • 接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
  • 发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
  • 接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。

可以看出,最重要的就是,SDA在SCL低电平由主机改变数据,SDA在高电平有从机读出数据。相比于FPGA中可以精细的控制SCL时钟,单片机并不是很在意SCL线周期是否稳定,只要高低电平对就行了。

下面介绍具体的数据帧结构。对于I2C通信来说,共有如下模式:

  • 写模式:字节写、页写。
  • 读模式:当前地址读、随机读、顺序读。

注:由于读模式中不能指定地址,所以“随机读”模式通过借鉴写模式,使用“哑写”改变当前的地址。

图10-6 IIC“字节写”的数据帧结构
图10-7 IIC“页写”的数据帧结构
图10-8 IIC“当前地址读”的数据帧结构
图10-9 IIC“随机读”的数据帧结构
图10-10 IIC“顺序读”的数据帧结构

由于目前不需要大数据量的通信,且I2C一般用于控制信号的传输,所以可以不考虑传输效率的问题;此外,为了方便进行模块化编程,所以每次读/写都希望可以指定一个地址,于是采用“字节写”、“随机读”两种模式作为读写操作。注意这两种方式每次都只能读/写1字节的信息。注意每次传输的第一个字节为器件地址的相关信息,格式如下:

10-11 IIC协议“器件地址”数据帧格式

10.2 AT24C02数据存储

需求:在LCD1602上显示5位数据(unsigned int),使用按键进行操作:

  • key1进行加一操作;
  • key2进行减一操作;
  • key3进行将此数据存储到EEPROM芯片(AT24C02)中;
  • key4将其从EEPROM芯片中读出来。

注:为了验证是否存储到了EEPROM中,可以将开发板断电后,再从EEPROM芯片中读取数据进行验证。

10-12 “AT24C02存储数据”代码调用关系

代码展示:
- main.c

#include <REGX52.H>
#include "LCD1602.h"
#include "PushKey.h"
#include "AT24C02.h"

void main(){
  unsigned int num = 0;//要存储的数据
  unsigned char num_H8, num_L8;//数据的高8位和低8位
  unsigned char err1,err2;//表示操作的错误
  unsigned char key;
  //LCD初始化
  LCD_Init();
  //计算两个字节
  num_H8 = num/256;
  num_L8 = num%256;
  while(1){
    key = PushKey();
    if(key){
      switch(key){
        case 1: //数据加一
          num++;
          num_H8 = num/256; num_L8 = num%256;
          LCD_ShowString(2,1,"          ");
          break;
        case 2: //数据减一
          num--;
          num_H8 = num/256; num_L8 = num%256;
          LCD_ShowString(2,1,"          ");
          break;
        case 3: //数据存储
          err1 = AT24C02_WriteByte(0x02,num_H8);
          err2 = AT24C02_WriteByte(0x03,num_L8);
          if((!err1) && (!err2)){LCD_ShowString(2,1,"Write OK! ");}
          else                  {LCD_ShowString(2,1,"Write ERR!");}
          break;
        case 4: //数据读取
          num_H8 = AT24C02_ReadByte(0x02);
          num_L8 = AT24C02_ReadByte(0x03);
          num = num_H8*256 + num_L8;
          LCD_ShowString(2,1,"Read OK!"); 
          break;
        default:;
      }
    }
    LCD_ShowNum(1,1,num,5);
  }
}

- AT24C02.h

#ifndef __AT24C02_H__
#define __AT24C02_H__

unsigned char AT24C02_WriteByte(unsigned char wr_addr,wr_byte);//字节写
unsigned char AT24C02_ReadByte(unsigned char rd_addr);//随机读
  
#endif

- AT24C02.c

#include <REGX52.H>

// 下面可能需要更改
/**********************************/
// 对常用总线重命名
sbit SCL = P2^1;
sbit SDA = P2^0;
# define AT24C02_WR_ADDR 0xa0
# define AT24C02_RD_ADDR 0xa1

// 等待写周期的延时函数
void AT24C02_Delay5ms(){//@11.0592MHz
	unsigned char i, j;
	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}
/**********************************/

// 固定格式的函数
/**********************************/
/**
  * @brief :采用I2C“字节写”的方式,向AT24C02写入1字节数据。
  * @param :wr_addr写入地址,wr_byte写入数据
  * @retval :代表写入是否成功。
 */
unsigned char AT24C02_WriteByte(unsigned char wr_addr,wr_byte){
  unsigned char err=0;
  unsigned char i;
  //起始条件
  SDA = 1;
  SCL = 1;
  SDA = 0;
  SCL = 0;
  //发送第一字节数据
  for(i=0;i<8;i++){
    SCL = 0;
    SDA = AT24C02_WR_ADDR&(0x80>>i);
    SCL = 1;
  }
  //接收第一次应答
  SCL = 0;
  SDA= 1; //开漏输出,释放SDA
  SCL = 1;
  if(SDA){err=1;}
  SCL = 0;
  if(err){return err;}
  //发送第二字节数据:地址
  for(i=0;i<8;i++){
    SCL = 0;
    SDA = wr_addr&(0x80>>i);
    SCL = 1;
  }
  //接收第二次应答
  SCL = 0;
  SDA= 1; //开漏输出,释放SDA
  SCL = 1;
  if(SDA){err=1;}
  SCL = 0;
  if(err){return err;}
  //发送第三字节数据:数据
  for(i=0;i<8;i++){
    SCL = 0;
    SDA = wr_byte&(0x80>>i);
    SCL = 1;
  }
  //接收第三次应答
  SCL = 0;
  SDA= 1; //开漏输出,释放SDA
  SCL = 1;
  if(SDA){err=1;}
  if(err){return err;}
  //终止条件
  SCL = 0;
  SDA = 0;
  SCL = 1;
  SDA = 1;
  //等待写周期完成
  AT24C02_Delay5ms();
  return err;
}

/**
  * @brief :采用I2C“随机读”的方式,从AT24C02指定地址读出1字节数据。
  * @param :rd_addr读出地址
  * @retval :读出的数据。
  * 一般读不要会出错,所以就不返回错误标志err了,但保留其定义。
 */
unsigned char AT24C02_ReadByte(unsigned char rd_addr){
  unsigned char rd_byte = 0x00;
  unsigned char err=0,wr_byte1=0xa0;
  unsigned char i;
  //起始条件
  SDA = 1;
  SCL = 1;
  SDA = 0;
  SCL = 0;
  //发送第一字节数据
  for(i=0;i<8;i++){
    SCL = 0;
    SDA = AT24C02_WR_ADDR&(0x80>>i);
    SCL = 1;
  }
  //接收第一次应答
  SCL = 0;
  SDA= 1; //开漏输出,释放SDA
  SCL = 1;
  err = SDA; 
  //发送第二字节数据:地址
  for(i=0;i<8;i++){
    SCL = 0;
    SDA = rd_addr&(0x80>>i);
    SCL = 1;
  }
  //接收第二次应答
  SCL = 0;
  SDA= 1; //开漏输出,释放SDA
  SCL = 1;
  err = SDA; 
  SCL = 0;
  //起始条件
  SDA = 1;
  SCL = 1;
  SDA = 0;
  SCL = 0;
  //发送第一字节数据
  for(i=0;i<8;i++){
    SCL = 0;
    SDA = AT24C02_RD_ADDR&(0x80>>i);
    SCL = 1;
  }
  //接收第三次应答
  SCL = 0;
  SDA= 1; //开漏输出,释放SDA
  SCL = 1;
  err = SDA;
  SCL = 0;
  //接收数据:数据
  for(i=0;i<8;i++){
    SCL = 0;
    if(SDA){rd_byte |= (0x80>>i);}
    SCL = 1;
  }
  //NO ACK:主机对SDA无动作
  SCL = 0;
  SDA = 1;
  SCL = 1;
  SCL = 0;
  //终止条件
  SCL = 0;
  SDA = 0;
  SCL = 1;
  SDA = 1;
  return rd_byte;
}

- PushKey.h

#ifndef __PUSHKEY_H__
#define __PUSHKEY_H__

// 延时cycles ms,晶振@11.0592MHz
void PushKey_Delay(unsigned char cycles){
  unsigned char i, j;
  do{
    i = 2;
    j = 199;
    do{
      while (--j);
    }while (--i);
  }while(--cycles);
}

/**
  * @brief :检测按下了哪个按键开关
  * @param :无
  * @retval :输出按键开关编号1~4,不按返回0,松开触发
 */
unsigned char PushKey(){
  unsigned char key = 0;
  if(!P3_1)     {PushKey_Delay(10);while(!P3_1);PushKey_Delay(10);key=1;}
  else if(!P3_0){PushKey_Delay(10);while(!P3_0);PushKey_Delay(10);key=2;}
  else if(!P3_2){PushKey_Delay(10);while(!P3_2);PushKey_Delay(10);key=3;}
  else if(!P3_3){PushKey_Delay(10);while(!P3_3);PushKey_Delay(10);key=4;}
  return key;
}

#endif

- LCD1602.h- LCD1602.c与第8节“8DS1302实时时钟”相同。

编程感想:

  • 单片机软件编程不用精细的考虑SCL线的变化,为了保证SDA的正确性,即使上一个步骤已经有SCL=0,下一个步骤的开始也可以再次执行SCL=0
  • 卡了很久的bug:写操作时,第三个字节写数据其实是在“写地址”,不知怎么就一直没看出来。😅
  • 卡了很久的bug:在说明书中提到,“字节写”结束后,EEPROM会进入写周期(大概5ms),直到内部写完成后才会响应新的请求。所以不能连续写两个字节!! 要给写周期留有一定的延时。
  • 卡了很久的bug:在读取数据的过程中,此语句进行赋值可行if(SDA){rd_byte |= (0x80>>i);},而后面这个语句则不可行rd_byte |= (SDA&(0x80>>i));!可见,软件编程中,跨位宽赋值时还是尽量用判断,避免位运算

10.3 AT24C02秒表(定时器扫描按键及数码管)

本实验重点在于使用定时器扫描按键和数码管,而不是像之前一样采用软件延时,从而会陷入到死循环当中。

  1. 对于按键来说。设置一个静态全局变量,表示上一次按下的按键编号。每次中断计时(如20ms)来临时,进入按键检测子函数,看看当前是否为上升沿,从而更新静态全局变量。主函数每次请求按键,则返回这个静态全局变量。如果想在数码管上长久地看到这个按键,那就将其归零的过程放在返回函数中,而不是PushKey_Loop()函数中。
  2. 对于数码管来说。设置一个8位静态全局数组,存储8个数码管所需要显示的数据。每次中断计时(如2ms)来临时,进入数码管刷新子函数。这个数码管刷新子函数只负责刷新数据缓冲区的数据(使用for保证每次只刷新一个数码管)。而对于这些数据的更改,则是在主函数中调用相应的函数实现。

需求:在数码管上显示秒表“时-分-秒”,并使用按键进行控制:

  • key1进行开始/暂停功能;
  • key2将当前时间清零;
  • key3将当前的数据写入到EEPROM芯片(AT24C02)中;
  • key4则从EEPROM中将数据读回来(需掉电验证)。
10-12 “AT24C02存储数据”代码调用关系

代码展示:
- main.c

#include <REGX52.H>
#include "Timer0.h"
#include "PushKey.h"
#include "NixieTube.h"
#include "AT24C02.h"

// 定义分/秒/10倍毫秒/秒表工作标志
unsigned char mins=0,secs=0,ms10=0,StopWatch_flag=0;

void main(){
  unsigned char num_key=0;
  //定时器T0初始化
  Timer0_Init();
  //数码管显示初始化
  NixieTube_SetBuf(6,11);
  NixieTube_SetBuf(3,11);
  while(1){
    num_key = PushKey_GetKey();
    switch(num_key){
      // 开始/暂停秒表
      case 1:
        StopWatch_flag = !StopWatch_flag;
        break;
      // 秒表归零
      case 2:
        ms10 = 0;
        secs = 0;
        mins = 0;
        break;
      // 将当前数据写入到EEPROM中
      case 3:
        AT24C02_WriteByte(0x00,ms10);
        AT24C02_WriteByte(0x01,secs);
        AT24C02_WriteByte(0x02,mins);
        break;
      // 从EEPROM读取当前数据
      case 4:
        ms10 = AT24C02_ReadByte(0x00);
        secs = AT24C02_ReadByte(0x01);
        mins = AT24C02_ReadByte(0x02);
        break;
      default:;
    }
    NixieTube_SetBuf(1,ms10%10);
    NixieTube_SetBuf(2,ms10/10);
    NixieTube_SetBuf(4,secs%10);
    NixieTube_SetBuf(5,secs/10);
    NixieTube_SetBuf(7,mins%10);
    NixieTube_SetBuf(8,mins/10);
  }
}

// 秒表更新函数
void StopWatch_Loop(void){
  if(ms10>=99){
    ms10 = 0;
    if(secs>=59){
      secs = 0;
      if(mins>=59){mins=0;}
      else        {mins++;}
    }
    else{secs++;}
  }
  else{ms10++;}
}

// 定义定时器T0中断后要执行的动作
void Timer0_Routine() interrupt 1{
  static unsigned int count1_T0,count2_T0,count3_T0; //中断次数
  TH0 = 0xfc; TL0 = 0x66; // 恢复溢出周期,近似1ms
  //数码管扫描
  count1_T0++; //数码管使用的中断
  if(count1_T0>1){
    count1_T0 = 0;
    NixieTube_Loop();
  }
  //按键扫描
  count2_T0++; //按键使用的中断
  if(count2_T0>20){
    count2_T0 = 0;
    PushKey_Loop();
  }
  //秒表计数
  count3_T0++;
  if(count3_T0>10){
    count3_T0 = 0;
    if(StopWatch_flag){StopWatch_Loop();}
  }
}

- NixieTube.h

#ifndef __NIXIETUBE_H__
#define __NIXIETUBE_H__

// 数据缓冲区,注意每个数字显示的范围就是0~9
unsigned char NixieTube_buf[8] = {1,2,3,4,5,6,7,8};

// 给出数字0~9的定义(符合数组的索引),最后两个依次表示不显示/显示横杠
unsigned char NixieTube_number[12] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00,0x40};


/**
  * @brief :改变数码管数据缓冲区的数值。
* @param :index索引1~8,num数值0~11(10表示不显示,11表示横杠)
  * @retval :无
 */
void NixieTube_SetBuf(unsigned char index,num){
  NixieTube_buf[index-1] = num;
}

/**
  * @brief :在指定数码管显示指定的数字。
  * @param :led片选信号0~7,num表示显示的索引0~11
  * @retval :无。
 */
void NixieTube_Signle(unsigned char led, unsigned char num){
  // 给出选择的LED1~LED8的定义(实际上只用P2的2/3/4引脚)
  unsigned char sel_led[8] = {0x00,0x04,0x08,0x0c,0x10,0x14,0x18,0x1c};
  P0 = 0x00; // 数码管不显示,消影
  P2 = sel_led[led]; // 选择数码管:LED1
  P0 = NixieTube_number[num];  // 数码管显示
}

//定时器中断函数执行的数码管扫描函数,每次只扫描一个数码管
void NixieTube_Loop(void){
  static unsigned char i=0;
  NixieTube_Signle(i,NixieTube_buf[i]);
  i++;
  if(i>=8){i=0;}
}

#endif

- PushKey.h

#ifndef __PUSHKEY_H__
#define __PUSHKEY_H__

// 上一次按下的按键
unsigned char num_PushKey=0;

/**
  * @brief :告诉主函数按下了哪个按键。
  * @param :无
  * @retval :按键编号1~4
 */
unsigned char PushKey_GetKey(void){
  unsigned char temp;
  temp = num_PushKey;
  num_PushKey = 0;
  return temp;
}

/**
  * @brief :检测按下了哪个按键开关
  * @param :无
  * @retval :输出按键开关编号1~4,不按返回0,松开触发
 */
unsigned char PushKey_SingleDetect(void){
  unsigned char key = 0;
  if(!P3_1)     {key=1;}
  else if(!P3_0){key=2;}
  else if(!P3_2){key=3;}
  else if(!P3_3){key=4;}
  return key;
}

//定时器中断函数执行的按键扫描函数
void PushKey_Loop(void){
  static unsigned char state_last=0, state_curr=0;
  state_last = state_curr;
  state_curr = PushKey_SingleDetect();
  if     (state_last==1 && state_curr==0){num_PushKey=1;}
  else if(state_last==2 && state_curr==0){num_PushKey=2;}
  else if(state_last==3 && state_curr==0){num_PushKey=3;}
  else if(state_last==4 && state_curr==0){num_PushKey=4;}
}

#endif

- Timer0.h

#ifndef __TIMER0_H__
#define __TIMER0_H__

#include <REGX52.H>
/**
  * @brief :对定时器0进行初始化,初始化完成后定时器0即可正常工作。
  * 注:对11.0592MHz进行12分频(脉冲周期1.0850694us)。
  * 注:配置过程中,由于掉电复位后中断都默认不开启,所以只需配置定时器0
  *     相关的寄存器即可,不要定义其他中断的寄存器,以保证程序的复用性。
 */
void Timer0_Init(){
  // 配置定时器T0的相关寄存器
  TMOD&=0xf0; TMOD|=0x01; // 选择T0的GATE=0/允许计数/模式1
  // 上面这个方法目的是不干扰高四位,对低四位先清零再加值。
  TF0 = 0; TR0 = 1; // 溢出标志位清空,运行控制位置1
  TH0 = 0xfc; TL0 = 0x66; // 离溢出近似1ms
  // 注:上面这个初值只在第一次溢出生效,后面都是从0开始计数。
  // 配置中断寄存器
  EA = 1; ET0 = 1; // 不屏蔽所有中断,允许T0溢出中断
  PT0 = 0; // T0优先级保持默认,不写这句话也可以
}

/*中断函数模板
// 定义定时器T0中断后要执行的动作
void Timer0_Routine() interrupt 1{
  static unsigned int count_T0; //中断次数
  count_T0++; //更新中断次数
  TH0 = 0xfc; TL0 = 0x66; // 恢复溢出周期,近似1ms
  if(count_T0>500){
    count_T0 = 0;
    
  }  
}
*/
#endif

AT24C02.hAT24C02.c与上一小节的实验相同。

编程感想:

  1. 小bug:注意staticconst不要写混了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/184660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

InfluxDB OSS v2.6.0安装使用小结(ubuntu Linux)

1 InfluxDB简介 InfluxDB是一款用Go语言编写的开源分布式时序、事件和指标数据库。 官网&#xff1a;https://www.influxdata.com 1.1 特色 InfluxDB的主要特色 1&#xff09;无结构&#xff08;无模式&#xff09;&#xff1a;可以是任意数量的列 2&#xff09;可拓展的 3&…

学习云原生的阅读书单

以下是我从豆瓣阅读上找到的书单 《云原生服务网格lstio》 《云原生操作系统Kubernetes》 《OpenShift云原生架构&#xff1a;原理与实践》

[oeasy]python0066_控制序列_光标位置设置_ESC_逃逸字符_CSI

光标位置 回忆上次内容 上次讲了 三引号的输出三引号中 回车和引号 都会 被原样输出\ 还是需要从 \\转义 黑暗森林 快被摸排清了 还有哪个 转义序列 没 研究过吗&#xff1f;&#x1f914; \e是 干什么的&#xff1f;&#x1f914; 回忆转义 转义转义 转化含义 \反斜杠(…

CnOpenData劳务外包企业工商注册基本信息数据

一、数据简介 随着我国社会主义市场经济的发展&#xff0c;劳务市场中的用工方式也朝着多样化方向演变&#xff0c;劳务外包正是现代化人力资源管理和企业生产实际结合的一种独特的新模式。 在劳务外包过程中&#xff0c;企业将人事管理的部分或者全部工作外包给一个专门的服务…

Hadoop 复习 ---- chapter01【大数据概念】

Hadoop 复习 ---- chapter01【大数据概念】1. 什么是大数据大数据的简介从IT过渡到DT2. Hadoop生态系统工具HADOOPHBASEHIVESTORMZooKeeperSqoopMAHOUT1. 什么是大数据 大数据的简介 指“无法由现有软件工具进行提取、存储、搜索、共享、分析和处理的庞大而复杂的数据集”。 通…

【软件测试】某城商行手机银行授权漏洞分析黑客攻击,测试优化手段......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 问题描述 据报道&am…

【FreeRTOS】详细讲解FreeRTOS的软件定时器及通过示例讲述其用法

软件定时器 所谓定时器&#xff0c;也就可以类比生活中人们常用的闹钟&#xff0c;可以单次响铃提醒&#xff0c;也可以间隔固定时间响铃提醒&#xff1b;与FreeRTOS定时器不同的是周期不同&#xff0c;FreeRTOS的周期更加短&#xff0c;一般使用毫秒(ms)、秒(s)。   软件定时…

Python的数字类型、布尔类型和运算优先级

文章目录1.数字类型1.1分类1.2整数1.3浮点数1.4复数2.数字运算符2.1运算符表格2.2 运算符 //3.divmod&#xff08;&#xff09;函数4.abs&#xff08;&#xff09;函数4. int()&#xff0c;float() 和 complex() 函数5.pow&#xff08;&#xff09;函数和运算符 **6.布尔类型6.…

NB-IoT的低功耗特性原理解说

什么是NB-IoT NB-IoT的中文名叫窄带蜂窝物联网(Narrow Band Internet of Things)&#xff0c;NB-IoT网络是基于4G网络演进过来的&#xff0c;所以它在上行和下行的复用技术上还是沿用了4G的OFDMA和SC-FDMA。NB-IoT有三大特性&#xff1a;速率低&#xff0c;成本低&#xff0c;…

电脑风扇声音大怎么办?具体原因以及解决措施,快速解决

​很多小伙伴使用台式电脑&#xff0c;使用的时间长了&#xff0c;电脑机箱里的风扇就会发出一些噪音&#xff0c;很影响小伙伴的整体使用体验。电脑风扇声音大怎么办&#xff1f;具体的原因以及解决措施有哪些&#xff1f;下面就跟着小编一起来看看吧。 一、电脑风扇声音大的原…

数据挖掘,计算机网络、操作系统刷题笔记39

数据挖掘&#xff0c;计算机网络、操作系统刷题笔记39 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;orac…

Kibana最新版8.6.1安装教程

Kibana 让您能够自由地选择如何呈现自己的数据。不过借助 Kibana 的交互式可视化&#xff0c;您可以先从一个问题出发&#xff0c;看看能够从中发现些什么。查看完整的 Kibana 功能列表https://www.elastic.co/cn/kibana/featuresKibana的下载地址&#xff1a;https://www.elas…

有状态/无状态认证

文章目录一、什么是有状态认证&#xff1f;二、什么是无状态认证&#xff1f;&#xff08;token&#xff09;三、无状态分布式认证解决方案一、什么是有状态认证&#xff1f; 有状态认证&#xff0c;即服务端需要记录每次会话的客户端信息&#xff0c;从而识别客户端身份&#…

几种最小二乘法及python代码:ELS、TLS、RLS

1.ARMAX模型 下面各章节&#xff0c;我就是使用上面公式的符号&#xff0c;其中y是输出&#xff0c;u是输入&#xff0c;e是噪声。有m个输出y&#xff0c;r个输入u。 进一步精简为&#xff1a; YPθE 其中&#xff1a;Y为要预测的部分&#xff0c;P为已知数据&#xff08;包…

k8s添加node节点和master节点

一.准备1.基本概述版本&#xff1a;kubelet&#xff1a;v1.20.4docker&#xff1a; 20.10.23资源&#xff1a;cpu&#xff1a;8mem&#xff1a;16kernel&#xff1a;3.10.0-1160.71.1.el7.x86_64镜像仓库地址&#xff1a;registry.cn-hangzhou.aliyuncs.com/google_containers/…

大部分人都容易焦虑,那么应该如何对待焦虑呢?

新年伊始&#xff0c;告别喜庆欢乐的春节&#xff0c;大家应该已经投入到正常的工作当中去了。面对节后的开工&#xff0c;难免都会有点焦虑&#xff0c;因为大多数人还沉浸在春节喜悦的回忆当中&#xff0c;回忆都是美好的&#xff0c;因为回忆中是带有感情的&#xff0c;美好…

总投资30亿、算力500P,宜昌先进计算产业可持续发展之路

近日&#xff0c;国家先进计算产业创新&#xff08;宜昌&#xff09;中心项目工程总承包和委托运营&#xff08;EPCO&#xff09;中标结果公示&#xff0c;由中科升哲数据科技有限公司联合重庆市设计院有限公司、中讯邮电咨询设计院有限公司、中国化学工程第十六建设有限公司组…

multipart/form-data 在低版本spring和webFlux中的解析

背景 最近在做一个技术项目的迁移&#xff0c;将老的springMVC项目迁移到SpringWebFlux项目中&#xff0c;在流量迁移过程中发现有一个业务方传过来的参数新项目拿不到&#xff0c;究其原因是老版本的spring解析器和新版本的解析器对multipart/form-data类型的contentType解析…

《深入浅出计算机组成原理》学习笔记 Day14

数据通路&#xff08;下&#xff09;1. PC 寄存器的实现2. 读写数据所需要的译码器3. 数据通路完整实现4. 总结参考1. PC 寄存器的实现 PC 寄存器又名程序计数器&#xff08;Program Counter&#xff09;。 PC 寄存器由两个部分组成&#xff1a; 时钟信号。提供定时的输入&a…

刚来的00后太卷了,上班还没2年,跳到我们公司起薪25k....

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。 这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作都没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了…