• Cortex-M架构系统定时器阻塞和非阻塞延时


    【Cortex-M架构】SysTick系统定时器阻塞和非阻塞延时

    阻塞延时

    首先是最常用的阻塞延时

    void delay_ms(unsigned int ms)
    {
    	SysTick->LOAD = 50000000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
    	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
    	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
    	while(ms--)
    	{
    		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
    	}
    	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
    }
    void delay_us(unsigned int us)
    {
    	SysTick->LOAD = 50000000/1000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
    	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
    	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
    	while(us--)
    	{
    		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
    	}
    	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    50000000表示工作频率
    分频后即可得到不同的延时时间
    以此类推

    那么 不用两个嵌套while循环 也可以写成:

    void delay_ms(unsigned int ms)
    {
    	SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
    	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
    	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
    
    	while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
    
    	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
    }
    void delay_us(unsigned int us)
    {
    	SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
    	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
    	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
    	
    	while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
    
    	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    但是这种写法有个弊端
    那就是输入ms后,最大定时不得超过计数值,也就是不能超过LOAD的最大值,否则溢出以后,则无法正常工作

    而LOAD如果最大是32位 也就是4294967295

    晶振为50M的话 50M的计数值为1s 4294967295计数值约为85s

    固最大定时时间为85s

    但用嵌套while的话 最大可以支持定时4294967295*85s

    非阻塞延时

    如果采用非阻塞的话 直接改写第二种方法就好了:

    void delay_ms(unsigned int ms)
    {
    	SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
    	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
    	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
    
    	//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
    
    	//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
    }
    void delay_us(unsigned int us)
    {
    	SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
    	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
    	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
    	
    	//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
    
    	//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    将等待和关闭定时器语句去掉
    在使用时加上判断即可变为阻塞:

    delay_ms(500);
    while ((SysTick->CTRL & 0x00010000)==0);
    SysTick->CTRL = 0;
    
    • 1
    • 2
    • 3

    在非阻塞状态下 可以提交定时器后 去做别的事情 然后再来等待

    不过这样又有一个弊端 那就是定时器会自动重载 可能做别的事情以后 定时器跑过了 然后就要等85s才能停下

    故可以通过内部定时器来进行非阻塞延时函数的编写

    基本上每个mcu的内部定时器都可以配置自动重载等功能 网上资料很多 这里就不再阐述了

    附录:压缩字符串、大小端格式转换

    压缩字符串

    首先HART数据格式如下:
    在这里插入图片描述
    在这里插入图片描述
    重点就是浮点数和字符串类型
    Latin-1就不说了 基本用不到

    浮点数

    浮点数里面 如 0x40 80 00 00表示4.0f

    在HART协议里面 浮点数是按大端格式发送的 就是高位先发送 低位后发送

    发送出来的数组为:40,80,00,00

    但在C语言对浮点数的存储中 是按小端格式来存储的 也就是40在高位 00在低位
    浮点数:4.0f
    地址0x1000对应00
    地址0x1001对应00
    地址0x1002对应80
    地址0x1003对应40

    若直接使用memcpy函数 则需要进行大小端转换 否则会存储为:
    地址0x1000对应40
    地址0x1001对应80
    地址0x1002对应00
    地址0x1003对应00

    大小端转换:

    void swap32(void * p)
    {
       uint32_t *ptr=p;
       uint32_t x = *ptr;
       x = (x << 16) | (x >> 16);
       x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);
    
       *ptr=x;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    压缩Packed-ASCII字符串

    本质上是将原本的ASCII的最高2位去掉 然后拼接起来 比如空格(0x20)
    四个空格拼接后就成了
    1000 0010 0000 1000 0010 0000
    十六进制:82 08 20
    对了一下表 0x20之前的识别不了
    也就是只能识别0x20-0x5F的ASCII表
    在这里插入图片描述

    压缩/解压函数后面再写:

    //传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
    uint8_t Trans_ASCII_to_Pack(uint8_t * str,uint8_t * buf,const uint8_t str_len)
    {
       if(str_len%4)
       {
          return 0;
       }
    	 
       uint8_t i=0;
       memset(buf,0,str_len/4*3);	  
       for(i=0;i<str_len;i++)
       {
          if(str[i]==0x00)
          {
             str[i]=0x20;
          }
       }
    
       for(i=0;i<str_len/4;i++)
       {
          buf[3*i]=(str[4*i]<<2)|((str[4*i+1]>>4)&0x03);
          buf[3*i+1]=(str[4*i+1]<<4)|((str[4*i+2]>>2)&0x0F);
          buf[3*i+2]=(str[4*i+2]<<6)|(str[4*i+3]&0x3F);
       }
    
       return 1;
    }
    
    //传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
    uint8_t Trans_Pack_to_ASCII(uint8_t * str,uint8_t * buf,const uint8_t str_len)
    {
       if(str_len%4)
       {
          return 0;
       }
    
       uint8_t i=0;
    
       memset(str,0,str_len);
    
       for(i=0;i<str_len/4;i++)
       {
          str[4*i]=(buf[3*i]>>2)&0x3F;
          str[4*i+1]=((buf[3*i]<<4)&0x30)|(buf[3*i+1]>>4);
          str[4*i+2]=((buf[3*i+1]<<2)&0x3C)|(buf[3*i+2]>>6);
          str[4*i+3]=buf[3*i+2]&0x3F;
       }
    
       return 1;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    大小端转换

    在串口等数据解析中 难免遇到大小端格式问题

    什么是大端和小端

    所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

    简单来说:大端——高尾端,小端——低尾端

    举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:

    1)大端模式:

    低地址 -----------------> 高地址

    0x12 | 0x34 | 0x56 | 0x78

    2)小端模式:

    低地址 ------------------> 高地址

    0x78 | 0x56 | 0x34 | 0x12

    可见,大端模式和字符串的存储模式类似。

    数据传输中的大小端

    比如地址位、起止位一般都是大端格式
    如:
    起始位:0x520A
    则发送的buf应为{0x52,0x0A}

    而数据位一般是小端格式(单字节无大小端之分)
    如:
    一个16位的数据发送出来为{0x52,0x0A}
    则对应的uint16_t类型数为: 0x0A52

    而对于浮点数4.0f 转为32位应是:
    40 80 00 00

    以大端存储来说 发送出来的buf就是依次发送 40 80 00 00

    以小端存储来说 则发送 00 00 80 40

    由于memcpy等函数 是按字节地址进行复制 其复制的格式为小端格式 所以当数据为小端存储时 不用进行大小端转换
    如:

    uint32_t dat=0;
    uint8_t buf[]={0x00,0x00,0x80,0x40};
       memcpy(&dat,buf,4);
       float f=0.0f;
       f=*((float*)&dat); //地址强转
       printf("%f",f);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    或更优解:

       uint8_t buf[]={0x00,0x00,0x80,0x40};   
       float f=0.0f;
       memcpy(&f,buf,4);
    
    • 1
    • 2
    • 3

    而对于大端存储的数据(如HART协议数据 全为大端格式) 其复制的格式仍然为小端格式 所以当数据为小端存储时 要进行大小端转换
    如:

    uint32_t dat=0;
    uint8_t buf[]={0x40,0x80,0x00,0x00};
       memcpy(&dat,buf,4);
       float f=0.0f;
       swap32(&dat); //大小端转换
       f=*((float*)&dat); //地址强转
       printf("%f",f);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    或:

    uint8_t buf[]={0x40,0x80,0x00,0x00};
       memcpy(&dat,buf,4);
       float f=0.0f;
       swap32(&f); //大小端转换
       printf("%f",f);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    或更优解:

    uint32_t dat=0;
    uint8_t buf[]={0x40,0x80,0x00,0x00};
       float f=0.0f;
       dat=(buf[0]<<24)|(buf[0]<<16)|(buf[0]<<8)|(buf[0]<<0)
       f=*((float*)&dat);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    总结

    固 若数据为小端格式 则可以直接用memcpy函数进行转换 否则通过移位的方式再进行地址强转

    对于多位数据 比如同时传两个浮点数 则可以定义结构体之后进行memcpy复制(数据为小端格式)

    对于小端数据 直接用memcpy写入即可 若是浮点数 也不用再进行强转

    对于大端数据 如果不嫌麻烦 或想使代码更加简洁(但执行效率会降低) 也可以先用memcpy写入结构体之后再调用大小端转换函数 但这里需要注意的是 结构体必须全为无符号整型 浮点型只能在大小端转换写入之后再次强转 若结构体内采用浮点型 则需要强转两次

    所以对于大端数据 推荐通过移位的方式来进行赋值 然后再进行个别数的强转 再往通用结构体进行写入

    多个不同变量大小的结构体 要主要字节对齐的问题
    可以用#pragma pack(1) 使其对齐为1
    但会影响效率

    大小端转换函数

    直接通过对地址的操作来实现 传入的变量为32位的变量
    中间变量ptr是传入变量的地址

    void swap16(void * p)
    {
       uint16_t *ptr=p;
       uint16_t x = *ptr;
       x = (x << 8) | (x >> 8);
    
       *ptr=x;
    }
    
    void swap32(void * p)
    {
       uint32_t *ptr=p;
       uint32_t x = *ptr;
       x = (x << 16) | (x >> 16);
       x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);
    
       *ptr=x;
    }
    
    void swap64(void * p)
    {
       uint64_t *ptr=p;
       uint64_t x = *ptr;
       x = (x << 32) | (x >> 32);
       x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF);
       x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF);
    
       *ptr=x;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    【面经】什么是回表?
    63基于matlab的生物地理的优化器(BBO)被用作多层感知器(MLP)的训练器。
    好用软件推荐
    Backbone 网络-ResNet v2 详解
    「强烈收藏」Python第三方库资源大全,1000+工具包
    七、Vue3基础之七
    自定义httpServletRequestWrapper导致上传文件请求参数丢失
    使用快解析搭建自己的minecraft服务器
    16位ADC的积分非线性做到0.5LSB的难度有多大
    <三>使用类模板实现STL Vector
  • 原文地址:https://blog.csdn.net/weixin_53403301/article/details/128133060