MCU中的LSB、MSB和大端模式、小端模式

打印 上一主题 下一主题

主题 1043|帖子 1043|积分 3129

第一章 LSB和MSB

1.1 最低有效位(Least Significant Bit, LSB)


红外接收器接收了0x45(0100 0101)之后,怎么将这个数据发送给MCU;
   LSB(least significant bit):最低有效位优先,例如红外通信是以最低有效位发送和接收的
  LSB发送的比特顺序: 1010 0010
  
当接收到按键数据时,红外接收头将数据按照从低到高,一个bit一个bit的将数据发生转发给MCU的GPIO口;
  1.1.1 MCU的接收示例 

  1. int32_t readIRData(uint8_t *pbuff){
  2.        
  3.         uint32_t timeout;                        // 超时时间
  4.         uint8_t tempData;                        // 保存临时的1Byte数据
  5.         int8_t i, j;                               
  6.         uint8_t *pData = pbuff;                // 使用一个指针来指向传递进来的数组       
  7.         /* 红外通讯协议要求引导码为低电平;
  8.         * 如果检测端口有高电平说明错误
  9.         */
  10.         if(PAin(8) == 1){
  11.                 return -1;
  12.         }
  13.         /* 检测端口低电平持续时间 */
  14.         timeout = 0;
  15.         while(PAin(8) == 0){
  16.                 timeout++;
  17.                 delay_us(10);
  18.                
  19.                 /* 红外接收头需要给端口输入9ms左右的低电平“引导码”
  20.                 * delay_us(10) * 1000 = 10000us = 10ms
  21.                 */
  22.                 if(timeout >= 1000){
  23.                         return -2;
  24.                 }
  25.         }
  26.        
  27.         /* 检测端口是高电平持续时间 */
  28.         timeout = 0;
  29.         while(PAin(8)){
  30.                 timeout++;
  31.                 delay_us(10);
  32.                
  33.                 /* 红外接收头需要给端口输入4.5ms左右的高电平“引导码”
  34.                 * delay_us(10) * 500 = 5000us = 5ms
  35.                 */
  36.                 if(timeout >= 500){
  37.                         return -3;
  38.                 }
  39.         }
  40.        
  41.        
  42.         /* 接收红外接收头的4Byte数据 = 地址码 + !地址码 + 功能码 + !功能码 */
  43.         for(j = 0; j < 4; j++){
  44.        
  45.                 /* 接收红外接收头的1Byte数据 */
  46.                 tempData = 0;
  47.                 for(i = 0; i < 8; i++){
  48.                         /* 检测端口是低电平持续时间 */
  49.                         timeout = 0;
  50.                         while(PAin(8) == 0){
  51.                                 timeout++;
  52.                                 delay_us(10);
  53.                                
  54.                                 /* 红外接收头需要给端口输入0.56ms(0.56ms * 1000 = 560us)左右的低电平时序*/
  55.                                 if(timeout >= 100){
  56.                                         return -4;
  57.                                 }
  58.                         }
  59.                        
  60.                         /*
  61.                         * 借助低电平在延时一段时间,检测再检测高电平还是低电平来判断是0还是1
  62.                         * 延时只需要在0.56~1.685ms之间即可
  63.                         * 如果高电平时间为0.56ms表示传输数据0, 1.685ms表示数据1;
  64.                         */
  65.                         delay_us(600);
  66.                        
  67.                        
  68.                         /* 延时600us后,检测再检测高电平还是低电平
  69.                         * 如果是高电平,认为红外接收头输入的是数据1,将数据1写入tempData
  70.                         * 否则tempData已经全部初始化为是0,所以不必理会。
  71.                         */
  72.                         if(PAin(8)){
  73.                                 tempData |= 1 << i;
  74.                                
  75.                                 /* 检测端口是高电平持续时间 */
  76.                                 timeout = 0;
  77.                                 while(PAin(8)){
  78.                                         timeout++;
  79.                                         delay_us(10);
  80.                                        
  81.                                         /* 已经延时了delay_us(600); 现在再延时2ms。
  82.                                         * 总的延时了2.6ms,远远大于1.685ms。所以要报错*/
  83.                                         if(timeout >= 200){
  84.                                                 return -5;
  85.                                         }
  86.                                 }
  87.                         }
  88.                 }
  89.                
  90.                 /* 保存一个字节的数据 */
  91.                 pData[j] = tempData;
  92.         }
  93.        
  94.         /* 红外接收头拉低电平50us,一次通讯结束 */
  95.         delay_us(50);
  96.        
  97.         /* 校验数据 */
  98.         if((pData[0] + pData[1]) == 0xFF){
  99.                 if((pData[2] + pData[3]) == 0xFF){
  100.                         return 0;
  101.                 }
  102.         }
  103.        
  104.         /* 返回错误 */
  105.         return -6;
  106. }
复制代码
1.1.2  MUC接收表示 



1.2 最高有效位(Most Significant Bit, MSB)


DHT11温湿度传感器一次完备的数据传输为40bit,高位先出。
数据格式:8bit湿度整数数据 + 8bit湿度小数数据 + 8bit温度整数数据 + 8bit温度小数数据 + 8bit校验和。
数据传送正确时校验和数据等于“8bit湿度整数数据 + 8bit湿度小数数据 + 8bit温度整数数据 + 8bit温度小数数据”所得效果的末8位。
温湿度传感器产生了一个字节的数据0xF4(1111 0100)之后,怎么将这个数据发送给MCU;
   MSB(Most Significant Bit):最高有效位优先,例如获取温湿度比特流数据的时间是以最高有效位接收的。
  MSB发送的比特顺序: 1111 0100
  
当传感器产生数据时,DHT11将数据按照从高到低,一个bit一个bit的将数据发生转发给MCU的GPIO口;
  1.2.1 MCU的接收示例

  1. int32_t readDHT11(uint8_t *pbuff){
  2.        
  3.         uint32_t timeout;                        // 超时时间
  4.         uint8_t tempData;                        // 保存临时的1Byte数据
  5.         int8_t i, j;                               
  6.         uint8_t *pData = pbuff;                // 使用一个指针来指向传递进来的数组       
  7.         uint16_t check_sum = 0;                // 定义一个变量。用于计数效验和
  8.         /* 配置端口为输出模式 */
  9.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  10.         GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  11.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  12.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  13.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  14.         GPIO_Init(GPIOG,&GPIO_InitStructure);
  15.        
  16.         /* 端口输出低电平 */
  17.         PGout(9) = 0;
  18.         delay_ms(20);
  19.        
  20.         /* 端口输出高电平 */
  21.         PGout(9) = 1;
  22.         delay_us(30);
  23.        
  24.         /* 将端口设置为输入模式,准备接受数据 */         
  25.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  26.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  27.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  28.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  29.         GPIO_Init(GPIOG, &GPIO_InitStructure);
  30.        
  31.        
  32.         /* 检测端口高电平持续时间 */
  33.         timeout = 0;
  34.         while(PGin(9)){
  35.                 timeout++;
  36.                 delay_us(1);
  37.                
  38.                 /* 高电平时间超过4000ms(DHT11数据手持可知道), 认为DHT11设备损坏 */
  39.                 if(timeout >= 4000){
  40.                         return -1;
  41.                 }
  42.         }
  43.        
  44.         /* 检测端口低电平持续时间 */
  45.         timeout = 0;
  46.         while(PGin(9) == 0){
  47.                 timeout++;
  48.                 delay_us(1);
  49.                
  50.                 /* DHT11需要给端口输入至少80us的低电平时序*/
  51.                 if(timeout >= 85){
  52.                         return -2;
  53.                 }
  54.         }
  55.        
  56.         /* 检测端口是高电平持续时间 */
  57.         timeout = 0;
  58.         while(PGin(9)){
  59.                 timeout++;
  60.                 delay_us(1);
  61.                
  62.                 /* DHT11需要给端口输入至少80us的高电平时序*/
  63.                 if(timeout >= 85){
  64.                         return -3;
  65.                 }
  66.         }
  67.        
  68.        
  69.         /* 接收DHT11的40bit(5Byte)数据 */
  70.         for(j = 0; j < 5; j++){
  71.        
  72.                 /* 接收DHT11的8bit(1Byte)数据 */
  73.                 tempData = 0;
  74.                 for(i = 7; i >= 0; i--){
  75.                         /* 检测端口是低电平持续时间 */
  76.                         timeout = 0;
  77.                         while(PGin(9) == 0){
  78.                                 timeout++;
  79.                                 delay_us(1);
  80.                                
  81.                                 /* DHT11需要给端口输入至少50us的低电平时序*/
  82.                                 if(timeout >= 60){
  83.                                         return -4;
  84.                                 }
  85.                         }
  86.                        
  87.                         /*
  88.                         * 借助低电平在延时一段时间,检测再检测高电平还是低电平来判断是0还是1
  89.                         * 延时只需要在28~70us之间即可
  90.                         * 如果高电平时间为26~28us表示传输数据0, 28~70us范围内表示数据1;
  91.                         */
  92.                         delay_us(40);
  93.                        
  94.                        
  95.                         /* 延时40us后,检测再检测高电平还是低电平
  96.                         * 如果是高电平,认为DHT11输入的是数据1,将数据1写入tempData
  97.                         * 否则tempData已经全部初始化为是0,所以不必理会。
  98.                         */
  99.                         if(PGin(9)){
  100.                                 tempData |= 1 << i;
  101.                                
  102.                                 /* 检测端口是高电平持续时间 */
  103.                                 timeout = 0;
  104.                                 while(PGin(9)){
  105.                                         timeout++;
  106.                                         delay_us(1);
  107.                                        
  108.                                         /* DHT11需要给端口输入大于70us的高电平时序,认为错误*/
  109.                                         if(timeout >= 75){
  110.                                                 return -5;
  111.                                         }
  112.                                 }
  113.                         }
  114.                 }
  115.                
  116.                 /* 保存一个字节的数据 */
  117.                 pData[j] = tempData;
  118.         }
  119.        
  120.         /* DHT11拉低电平50us,一次通讯结束 */
  121.         delay_us(50);
  122.        
  123.         /* 判断效验和 */
  124.         check_sum = (pData[0] + pData[1] + pData[2] + pData[3]) & 0xFF;
  125.         if(check_sum != pData[4]){
  126.                 return -6;
  127.         }
  128.        
  129.         /* 全部正确,返回0 */
  130.         return 0;
  131. }
复制代码
1.2.2  MUC接收表示


第二章 大端模式和小端模式

2.1 大端模式

在大端模式下,多字节数据的高字节存储在低所在,低字节存储在高所在。也就是说,数据的高位在内存中存放得较前。
举个例子,如果要存储 32 位的十六进制数 0x12345678,在大端模式下,它会被按以下顺序存储在内存中:
   所在:    ...     0x01    0x02    0x03    0x04
数据:   ...      0x12    0x34    0x56    0x78
  在内存中的存储顺序是从高位到低位,先存储 0x12(最高字节),再存储 0x34,依此类推。
2.1.1 Modbus通讯协议

Modbus协议使用的是大端模式来表示16位和32位的数据类型。

  • 16位数据---2Byte(表示一个寄存器数据)
例如,Modbus中读取的16位数据 0x1234 将会按以下方式传输:


  • 高字节(MSB):0x12
  • 低字节(LSB):0x34
因此,数据将会被按以下顺序传输:0x12 0x34(大端模式)。

  • 32位数据----4byte (表示2个寄存器数据)
如果Modbus必要传输32位数据,协议也会将其按照大端模式进行存储,即先发送高字节,再发送低字节。例如,32位数据 0x12345678 会按以下顺序传输:


  • 高字节:0x12
  • 次高字节:0x34
  • 次低字节:0x56
  • 低字节:0x78
传输顺序为:0x12 0x34 0x56 0x78(大端模式)。

 


 2.1.1.1 ModbusRTU主机

  1. #include <stdio.h>
  2. #include <stdint.h>
  3. // 定义宏 MAKEWORD 来组合两个字节
  4. #define MAKEWORD(a, b) ((uint16_t)(((uint8_t)(a)) | ((uint16_t)((uint8_t)(b))) << 8))
  5. // CRC计算函数(标准的 CRC-16-IBM 算法)
  6. uint16_t crc16(const uint8_t *data, uint16_t length) {
  7.     uint16_t crc = 0xFFFF;
  8.     for (uint16_t i = 0; i < length; ++i) {
  9.         crc ^= data[i];
  10.         for (uint8_t j = 0; j < 8; ++j) {
  11.             if (crc & 0x0001) {
  12.                 crc = (crc >> 1) ^ 0xA001;
  13.             } else {
  14.                 crc >>= 1;
  15.             }
  16.         }
  17.     }
  18.     return crc;
  19. }
  20. int main() {
  21.     // 1. 从站地址:0x01
  22.     // 2. 功能码:0x03
  23.     // 3. 起始寄存器地址:MAKEWORD(0x00, 0x01) => 0x0001
  24.     // 4. 寄存器数量:MAKEWORD(0x00, 0x02) => 0x0002
  25.     // 定义一个足够大的数组来存放 Modbus RTU 请求帧
  26.     uint8_t request_frame[8];
  27.     // 填充请求帧数据
  28.     request_frame[0] = 0x01;   // 从站地址
  29.     request_frame[1] = 0x03;   // 功能码
  30.     request_frame[2] = (uint8_t)(MAKEWORD(0x00, 0x01) >> 8); // 起始寄存器地址高字节
  31.     request_frame[3] = (uint8_t)(MAKEWORD(0x00, 0x01) & 0xFF); // 起始寄存器地址低字节
  32.     request_frame[4] = (uint8_t)(MAKEWORD(0x00, 0x02) >> 8); // 寄存器数量高字节
  33.     request_frame[5] = (uint8_t)(MAKEWORD(0x00, 0x02) & 0xFF); // 寄存器数量低字节
  34.     // 计算 CRC 校验码
  35.     uint16_t crc = crc16(request_frame, 6); // CRC计算不包含 CRC 字节
  36.     request_frame[6] = (uint8_t)(crc & 0xFF);       // CRC 低字节
  37.     request_frame[7] = (uint8_t)((crc >> 8) & 0xFF); // CRC 高字节
  38.     // 输出 Modbus RTU 请求帧
  39.     printf("Modbus RTU Request Frame: ");
  40.     for (int i = 0; i < 8; i++) {
  41.         printf("%02X ", request_frame[i]);
  42.     }
  43.     printf("\n");
  44.     // 发送数据(在实际应用中可以通过串口发送)
  45.     // send_data(request_frame, 8); // 伪代码,实际发送数据的函数
  46.     return 0;
  47. }
复制代码
  Modbus RTU Request Frame: 01 03 00 01 00 02 F7 D4
  
2.1.1.2 ModbusRTU从机



  • 主机发送



  • 从机剖析 
  1. #define MAKEWORD(a, b)      ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8))
复制代码
假设你调用 MAKEWORD(0x12, 0x34),那么:


  • a = 0x12(低字节)
  • b = 0x34(高字节)
过程如下:

  • 低字节: (BYTE)(a) 效果是 0x12。
  • 高字节: (WORD)((BYTE)(b)) << 8 效果是 0x34 << 8 = 0x3400。
   效果是:0x34 12
  使用上述宏界说剖析主机发送来的帧;
  1. typedef struct TModbusProtocol
  2. {
  3.         /* 缓冲区 */
  4.         BYTE pRxd[256];                                        /* 接受帧缓冲地址 */
  5.         BYTE pTxd[256];                                        /* 发送帧缓冲地址 */
  6.         BYTE byAddress;                                 /* 装置地址 */
  7.         /* 浏览结构 */
  8.         DWORD dwEventSend;                                /* 动作报告浏览指针 */
  9.         /* 计数器 */
  10.         WORD wErrorCount;                                /* 异常报文计数,CPT3 */
  11.         WORD wSuccessCount;                                /* 成功报文计数,CPT4 */
  12.         DWORD dwNetStateCount;                        /* 网络状态 */
  13. }TModbusProtocol;
  14. static TModbusProtocol me;
  15. static BYTE* s_pRxd;                         /* 接受帧缓冲地址 */
  16. static BYTE s_byWritePtr;                /* 发送缓冲区写指针 */
  17. /* modbus归约处理 */
  18. BOOL ManageModbusProtocol(void){
  19.     .........
  20.         /* 读取接收帧数据 */
  21.         dwLen = hw485_read(me.pRxd, 256);
  22.         s_pRxd = me.pRxd;
  23.     .........
  24. }
  25. /* 读取多个Hold Register */
  26. static BYTE _Frame_03_ReadHoldRegisters(TModbusProtocol* me)
  27.     WORD wStartAddr = MAKEWORD(s_pRxd[3], s_pRxd[2]);        // 0x00 01
  28.     WORD wCount = MAKEWORD(s_pRxd[5], s_pRxd[4]);            // 0x00 01
  29.     /* 个数判断 */
  30.     if (wCount < 1 || wCount > MAX_HOLDING_REGISTER_COUNT)
  31.             return ExeptionCode_3_ValidateDataValue;
  32.     /* 准备工作 */
  33.     me->pTxd[s_byWritePtr++] = wCount * 2;                /* BYTE Count */
  34. }
复制代码
 
2.2 小端模式

小端模式模式下,多字节数据的**低字节(LSB)**存储在低所在,**高字节(MSB)**存储在高所在。即数据的低位存放得较前。
同样以 0x12345678 为例,在小端模式下,它会被按以下顺序存储:
   内存存储顺序:
所在:    ...     0x01    0x02    0x03    0x04
数据:   ...    0x78    0x56    0x34    0x12
  2.3 总结

网络字节序、Modbus协议都是大端模式;
主机字节序是小端模式;

 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

北冰洋以北

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表