串口接收十六进制代码控制点灯-Arduino中文社区 - Powered by Discuz! Archiver

topdog 发表于 2020-11-19 23:57

串口接收十六进制代码控制点灯

本帖最后由 topdog 于 2021-1-24 15:27 编辑

设问串口接收十六进制的一串代码:00 00 00 00 00 00 FA 01,需要截取FA此段位置,即将00~FF转换成对应的十进制0~255,控制灯的亮度。



1,从串口读取单个的字符,把它们连接成字符串inputString。


2,Arduino在处理字符串有自己独特的语法,使用起来非常方便。譬如截取F,它在inputString字符串中第18位,至第19位截取,所以写成inputString.substring(18, 19)。第一个0的位置它的序列号是0呀!

3,十六进制FA,规范的写法是0xFA,对应的十进制值是250。十六进制的F表示15,A表示10,十六进制(HEX)转换十进制(DEC)公式:F*16^1+A*16^0=250 。

4,此程序中使用Aduino字符串的法言法语自定义函数int HexChangeDec(String ch),大家可以玩味一下。String虽然截取了一个字符但是在程序中依然是字符串类型(string),不是字符类型(char)。


const int LedPin = 9;
String inputString = "";
bool Status = false;

void setup()
{
Serial.begin(9600);
pinMode(LedPin, OUTPUT);
analogWrite(LedPin, 0);
inputString.reserve(200);
}

void loop()
{
if (Status)
{
    //Serial.println(inputString);
    String Ten = inputString.substring(18, 19);
    String Unit = inputString.substring(19, 20);
    int Val = HexChangeDec(Ten) * 16 + HexChangeDec(Unit);
    //Serial.println(Val);
    analogWrite(LedPin, Val);

    inputString = "";
    Serial.flush();

    Status = false;
}
}

int HexChangeDec(String ch)
{
String Hex0 = "0123456789ABCDEF";
for (int i = 0; i < Hex0.length() + 1; i++)
{
    String Hex1 = Hex0.substring(i, i + 1);
    if (ch == Hex1)
    {
      return i;
    }
}
}

void serialEvent()
{
if (Serial.available())
{
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n')
    {
      Status = true;
    }
}
}


5,举一反三,我们还可以使用字符串处理函数charAt(),截取字节类型,再结合ASCII编码的序列计算出十进制值。charAt()应用时字符在字符串中的实际位置截取。例如:00 00 00 00 00 00 FA 01里面,截取F, 即inputString.charAt(18)。

6,ASCII编码是由二进制表示的,譬如'0',二进制就是0011 0000,用十进制表示就是48。再如‘A’,二进制就是0100 0001,用十进制表示就是65。

7,0~9的ASCII编码减去48(字符0作为基准)一定小于10,字符编码和A字符编码之差就是十进制的值。另外A~F的ASCII编码减去48一定大于10,9到A还间隔了7个字符,所以减去7,才能求得对应的十进制值。

8,本例(ch - '0' > 10) ? ch - '0' - 7 : ch - '0' 是三元运算符,她是软件编程中的一个固定格式,语法是“条件表达式?表达式1:表达式2”。使用这个算法可以使调用数据时逐级筛选。条件表达式为真,输出为表达式1的值;条件表达式为假,输出为表达式2的值。


const int LedPin = 9;
String inputString = "";
bool Status = false;

void setup()
{
Serial.begin(9600);
pinMode(LedPin, OUTPUT);
analogWrite(LedPin, 0);
inputString.reserve(200);
}

void loop()
{
if (Status)
{
    //Serial.println(inputString);
    char Ten = inputString.charAt(18);
    char Unit = inputString.charAt(19);
    int Val = HexChangeDec(Ten) * 16 + HexChangeDec(Unit);
    //Serial.println(Val);
    analogWrite(LedPin, Val);

    inputString = "";
    Serial.flush();

    Status = false;
}
}

int HexChangeDec(char ch)
{
return (ch - '0' > 10) ? ch - '0' - 7 : ch - '0';
}

void serialEvent()
{
if (Serial.available())
{
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n')
    {
      Status = true;
    }
}
}
9,打开串口监视器,请注意程序里面 inChar == '\n','\n'为换行符,所以要在右下角选择换行符,这样就可以直接在串口输入行键入编码实现控制了。
10,UNO支持analogWrite()功能的管脚是3、5、6、9、10、11,带有~符号标记。












lanceli 发表于 2020-11-20 00:48

这样似乎效率更高? int o = (ch-'0' > 10) ? ch-'A'+10 : ch-'0';

redtxd 发表于 2020-11-20 12:36

感谢老师的讲解,非常实用的案例。

redtxd 发表于 2020-11-20 15:41

还有个问题麻烦老师,这个例子我测试几遍,如果数据不断变化的话,必须把串口关闭,再打开才能收到新的数据。

topdog 发表于 2020-11-21 00:22

本帖最后由 topdog 于 2020-11-21 00:32 编辑

redtxd 发表于 2020-11-20 15:41
还有个问题麻烦老师,这个例子我测试几遍,如果数据不断变化的话,必须把串口关闭,再打开才能收到新的数据 ...
打开串口监视器,请注意程序里面 inChar == '\n','\n'为换行符,所以要在右下角选择换行符,这样就可以直接在串口输入行键入编码实现控制了。
你也可以改成inChar == '\r','\r'为回车符,那么就要右下角选择回车符,在你按发送的时候,IDE会自动给你加回车键。

topdog 发表于 2020-11-21 00:23

lanceli 发表于 2020-11-20 00:48
这样似乎效率更高? int o = (ch-'0' > 10) ? ch-'A'+10 : ch-'0';

谢谢lanceli的关心和指导,一起学习一起进步。

topdog 发表于 2020-11-21 00:27

本帖最后由 topdog 于 2020-11-21 00:48 编辑

redtxd 发表于 2020-11-20 12:36
感谢老师的讲解,非常实用的案例。
串口控制的知识点还是比较多的,未能覆盖之处还请海涵,希望能够对你的学习有所帮助。

redtxd 发表于 2020-11-27 18:15

本帖最后由 redtxd 于 2020-11-27 18:27 编辑

   谢谢老师的热心帮助,还是在你的帖子上请教吧。:$
      两个例子我都试验过,都能正常控制。但是加入我做的程序中就无法实现,主要还是我的基础太差:L

      下面详细给老师说一下我想实现的功能:
       我采用Arduino nano、CAN-TLL模块,想通过CAN模块接收CAN总线上帧ID为48的数据
    (已通过设置CAN模块实现帧ID的过滤)如下图:

      如果CAN模块数据从软串口(2,3)输入,通过arduino的USB口接收数据正常,代码和接收情况如下:
/*
Arduino UNO软串口通信
*/
#include <SoftwareSerial.h>
SoftwareSerial CanSerial(2, 3); // RX, TX    CAN模块接软串口
void setup()
{
Serial.begin(115200);
CanSerial.begin(115200);      //初始化软串口

}
void loop()
{
if (CanSerial.available())
    Serial.write(CanSerial.read());
}

      
   
    但是加入取值就不行了,硬串口接收不到数据,灯也不受控:funk:,程序如下:

#include <SoftwareSerial.h>
SoftwareSerial CanSerial(2, 3); // RX, TX
const int LedPin = 11;
String inputString = "";
bool Status = false;
void setup()
{
Serial.begin(115200);
CanSerial.begin(115200);
pinMode(LedPin, OUTPUT);
analogWrite(LedPin, 0);
inputString.reserve(200);
}
void loop()
{
if (Status)
{
      String Ten = inputString.substring(18, 19);
    String Unit = inputString.substring(19, 20);
    int Val = HexChangeDec(Ten) * 16 + HexChangeDec(Unit);
    Serial.println(Val);                      //将转换后的数据在(0,1)显示
    analogWrite(LedPin, Val);
    inputString = "";
    CanSerial.flush();
    Serial.flush();
    Status = false;
}
}
int HexChangeDec(String ch)
{
String Hex0 = "0123456789ABCDEF";
for (int i = 0; i < Hex0.length() + 1; i++)
{
    String Hex1 = Hex0.substring(i, i + 1);
    if (ch == Hex1)
    {
      return i;
    }
}
}
void serialEvent()
{
if (CanSerial.available())
{
    char inChar = (char)CanSerial.read();
    inputString += inChar;
    if (inChar == '\n')
    {
      Status = true;
    }
}
}


topdog 发表于 2020-11-27 20:15

本帖最后由 topdog 于 2020-11-28 15:06 编辑

redtxd 发表于 2020-11-27 18:15
谢谢老师的热心帮助,还是在你的帖子上请教吧。
      两个例子我都试验过,都能正常控制。但是加入 ...
你瞧从sscom这张图片看勾选了加回车换行,再从你的程序Serial.write(CanSerial.read())分析,两条接收的字符串都换行了,说明上位机发字符串尾部是加了回车符,文章中第九点和讨论区第五楼都提到过了,请你换成 inChar == '\n' || inChar == '\r'就ok了!

锅小六 发表于 2022-3-25 16:52

想问一些怎么接收 01 02 00 8F 4A这个多个数组,比如要接收5个,每接收一组就存储一下,最后在提取有用的进行计算,这个怎么实现啊
页: [1] 2
查看完整版本: 串口接收十六进制代码控制点灯