You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

0-255数值转单个字符异常及UART传输STM32接收问题求助

串口字节传输问题排查与解决

问题描述

我尝试将0至255的数值转换为单个字符,使用chr()函数时发现例如255这类数值会被转换为双字符字符串。将这些字符拼接成字符串后,通过bytes(string, 'UTF-8')转换为字节流并发送至STM32的UART串口,但接收端无任何响应。也曾尝试构建包含0-255 uint8类型元素的字节数组进行传输,依然无法在STM32端接收到数据。

更新:当使用LATIN-1编码时,0-255的数值可以以单字节形式传输,但单独发送LATIN-1编码字节数组中的单个元素时却无法正常工作,请问这是什么原因?

原始代码片段

#!/usr/bin/python
import os
import sys
import serial

def asbyte(v):
    return chr(v & 0xFF)
def printStdErr(*objs):
#    print("", *objs, file=stderr)
    print("")
class LightYModem:
    soh = 1     # 128 byte blocks
    stx = 2     # 1K blocks
    eot = 4
    ack = 6
    nak = 0x15
    ca =  0x18          # 24
    crc16 = 0x43        # 67
    abort1 = 0x41       # 65
    abort2 = 0x61       # 97

    packet_len = 1024
    expected_packet_len = packet_len+5
    packet_mark = stx

    def __init__(self):
        self.seq = None
        self.ymodem = None

    def blocking_read(self):
        ch = ''
        while not ch:
            ch = self.ymodem.read(1)
        printStdErr("read %d " % ord(ch))
        return ch

    def write(self, packet):
            #print(packet)
            b = bytes(packet, 'UTF-8')
            print(b)

            self.ymodem.write(b)

            return len(packet);

    def flush(self):
        pass
        #self.ymodem.flush()

    def _read_response(self):
        ch1 = self.blocking_read()
        ch1 = ord(ch1)
        printStdErr("response %d" % (ch1))

        if ch1==LightYModem.ack and self.seq==0:    # may send also a crc16
            ch2 = self.blocking_read()
        elif ch1==LightYModem.ca:                   # cancel, always sent in pairs
            ch2 = self.blocking_read()
        return ch1

    def transfer(self, file, ymodem, output):
        self.ymodem = ymodem
        """
        file: the file to transfer via ymodem
        ymodem: the ymodem endpoint (a file-like object supporting write)
        output: a stream for output messages
        """

        file.seek(0, os.SEEK_END)
        size = file.tell()
        file.seek(0, os.SEEK_SET)
        response = self.send_filename_header("binary", size)
        while response==LightYModem.ack:
            response = self.send_packet(file, output)

        file.close()
        if response==LightYModem.eot:
            self._send_close()

        return response
    def _send_ymodem_packet(self, data):
        # pad string to 1024 chars
        data = data.ljust(LightYModem.packet_len)
        seqchr = asbyte(self.seq & 0xFF)
        print("seqchr : "+str(len(seqchr)))
        seqchr_neg = asbyte((255-self.seq) & 0xFF)
        print("seqchr_neg : "+str(len(seqchr_neg)))
        crc16 = '\x00\x00'
        print("bytes "+ str(bytes([255])))
        packet = asbyte(LightYModem.packet_mark) + seqchr + seqchr_neg + data + crc16
        if len(packet)!=LightYModem.expected_packet_len:
            raise Exception("packet length is wrong!")
        self.packet = packet
        written = self.write(packet)
        printStdErr("sent packet data, flush..."+str(written))
        self.flush()
        printStdErr("wait response..")
        response = self._read_response()
        if response==LightYModem.ack:
            ("sent packet nr %d " % (self.seq))
            self.seq += 1
        return response
    def send_packet(self, file, output):
        response = LightYModem.eot
        data = file.read(LightYModem.packet_len)
        if len(data):
            response = self._send_ymodem_packet(data)
        return response
    

    def send_filename_header(self, name, size):
        self.seq = 0
        packet = name 
        packet += asbyte(0) 
        packet += str(size) 
        packet += ' '
        return self._send_ymodem_packet(packet)

    def transfer(self, file, ymodem, output):
        self.ymodem = ymodem
        """
        file: the file to transfer via ymodem
        ymodem: the ymodem endpoint (a file-like object supporting write)
        output: a stream for output messages
        """

        file.seek(0, os.SEEK_END)
        size = file.tell()
        file.seek(0, os.SEEK_SET)
        response = self.send_filename_header("binary", size)
        while response==LightYModem.ack:
            response = self.send_packet(file, output)

        file.close()
        if response==LightYModem.eot:
            self._send_close()

        return response
def ymodem(args):
    ser = serial.Serial('COM24', 115200, serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE)
    os.chdir(r'D:\projects\testprojects\code\Test1\config')
    file = open(os.getcwd()+'\testfile.bin', 'rb')
    result = LightYModem().transfer(file, ser, sys.stderr)
    file.close()
    print("result: " + str(result))

    try:
        while (True):
            print(ser.read())
    except:
        pass
    print("Done")

if __name__ == '__main__':
    ymodem(sys.argv)

更新后的测试代码片段

seqchr ='\x00'
        seqchr_neg = '\xFF'
        packet =  seqchr + seqchr_neg 


...


    def write(self, packet):
        bh = bytearray(packet, 'LATIN-1')
        ch = bytes(packet[0], 'LATIN-1')
        print(ch)
        print(bh[0])
        self.ymodem.write(bh )#receive only zero on my STM
        self.ymodem.write(ch)# receive the 0x02 on my STM

核心原因分析

  1. UTF-8编码不匹配:UTF-8对0x80以上的字符会采用多字节编码(比如0xFF会被编码为0xC3 0xBF两个字节),这和UART单字节传输的需求完全冲突,STM32端接收的是错误的多字节数据,无法正确解析。
  2. 字符串与字节的混淆:Python的字符串是Unicode字符序列,并非原始字节序列。用chr()生成的是Unicode字符,转码时会受编码规则影响,不能直接用来表示底层字节。
  3. 单元素传输的写法错误:更新代码中bytes(packet[0], 'LATIN-1')属于误用——虽然能得到单字节,但逻辑绕弯且容易出错,正确的单字节生成方式应该直接基于数值构造。

解决方案

1. 全程使用字节类型处理,放弃字符串中转

串口传输本质是字节流,直接用bytes或bytearray类型操作,避免字符串转码的干扰:

  • 替换asbyte函数:
def asbyte(v):
    return bytes([v & 0xFF])  # 直接返回单字节bytes对象
  • 修改write方法,直接写入字节数据:
def write(self, packet):
    # 统一转成字节类型发送
    if isinstance(packet, str):
        packet = packet.encode('LATIN-1')
    elif not isinstance(packet, (bytes, bytearray)):
        packet = bytearray(packet)
    self.ymodem.write(packet)
    self.ymodem.flush()  # 确保数据全部发送
    return len(packet)

2. 修正YModem包的构建逻辑

直接用字节拼接代替字符串拼接,避免编码问题:

def _send_ymodem_packet(self, data):
    # 确保data是字节类型
    if isinstance(data, str):
        data = data.encode('LATIN-1')
    # 补全到1024字节
    data = data.ljust(LightYModem.packet_len, b'\x00')
    seq = self.seq & 0xFF
    seq_neg = (255 - seq) & 0xFF
    # 直接构建字节包
    packet = bytes([LightYModem.packet_mark]) + bytes([seq]) + bytes([seq_neg]) + data + b'\x00\x00'
    if len(packet) != LightYModem.expected_packet_len:
        raise Exception("packet length is wrong!")
    written = self.write(packet)
    printStdErr("sent packet data, flush..."+str(written))
    printStdErr("wait response..")
    response = self._read_response()
    if response == LightYModem.ack:
        printStdErr("sent packet nr %d " % (self.seq))
        self.seq += 1
    return response

3. 正确发送单个字节

如果需要单独发送单个字节,直接用数值构造字节对象:

# 发送单个字节0x00
self.ymodem.write(bytes([0x00]))
# 发送单个字节0xFF
self.ymodem.write(bytes([0xFF]))

额外注意事项

  • 确认STM32端的串口配置与Python端完全一致:波特率115200、8位数据位、无校验、1位停止位。
  • 发送后必须调用flush(),确保所有数据都被写入串口缓冲区并发送。
  • 检查STM32端的UART接收逻辑:确认缓冲区大小足够,且能正确解析YModem协议的帧头、序列号等字段。

内容的提问来源于stack exchange,提问作者user3719581

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:37:32