파이썬 시리얼 통신 (데이타 손실)

millelove의 이미지

안녕하세요. 파이선을 이용하여 시리얼 통신을 하고 있습니다. (디바이스 --> USB-serial convertor --> 컴퓨터 )
짧은 데이타를 전송하거나 받을때는 잘됩니다. 그러나 66MByte의 정보를 디바이스에서 컴퓨터로 받을때, 간혹 데이타를 잃어 버리는 문제가 발생합니다. 어쩔때는 10Byte를 잃어 버리고, 어쩔때는 100Byte를 잃어 버리기도 합니다. 이 숫자는 랜덤합니다. 동일한 환경에서 Uart 터미널로 데이타를 받게 될 경우에는 데이타 손실이 없습니다. 저의 코드와 Uart 터미널의 차이점을 생각해본 결과, Uart 터미널을 실행하게 될 경우에는 CPU 사용량이 35%정도가 됩니다. 반면 저의 파이썬 코드를 사용할 경우에는 1.5%의 CPU를 사용하고 있습니다.
저의 파이썬 코드의 우선순위를 높이고, CPU사용량을 늘리고 싶습니다. 그렇게하면 데이타 손실없이 받아 질것으로 예상하고 있습니다.
즉, 파이썬을 이용하여 시리얼 통신으로 데이타를 받을때, 데이타 손실을 없애고 싶습니다. 이를 위한 방법으로 파이썬 코드의 CPU의 사용량을 늘리고, 우선순위를 최우선으로 주고 싶습니다. (다른 인터럽트가 들어오는것을 막기 위해서.)
어떻게 하면 좋을까요? 조언 부탁드립니다.

라스코니의 이미지

제 생각에 CPU 사용량은 해결책이 아닙니다.
파이선에서 받을 때 도중에 버퍼 오버플로우가 일어나서 놓치는 것이 없는지 확인해 보세요.
받을 때 어떤 버퍼를 쓰시나요? ring buffer 인가요?

millelove의 이미지

시리얼로 데이타를 받을때, 2종류의 버퍼가 있습니다. 하나는 response이고, 다른하나는 data_buffer입니다. (아래 소스코드 참조)
우선 시리얼로 받은 데이타는 response에 갑니다. 테스트를 해본 결과, response에서 받을 수 있는 최대는 5xxxByte였습니다. 그래서 제가 읽어야 할 총 데이타의 양을 알고 있기에, 이 양의 데이타를 4096번씩 나눠서 읽습니다. 즉, 총데이타를 여러번 루프를 돌면서 매번 4096byte를 읽어 오고 있습니다. 즉, response는 매번 4096byte의 데이타만 저장합니다. 그리고 이 response가 data_buffer에 쌓이고 있습니다. data_buffer같은 경우에는 최대 ~68MByte 까지의 데이타를 저장합니다. data_buffer의 용량이 커서 data_buffer에서 잃어 버릴 가능성도 있지만, 확인해본결과, response에서 잃어 버립니다. data_buffer에서는 잃어 버리지 않습니다.
만약 오버플로우가 발생했다면 어떻게 하면 되나요?
아래에 코드 첨부합니다.
loop_1=data_size//4096
for i in range (0,loop_1)
[tap] ser.write(bytearray.fromhex('b8b803'+str('00001000'))) # 4096 byte의 데이타를 보내라고 하는 부분입니다.
[tap] ser.flushInput()
[tap] ser.flushOutput()
[tap] response=''
[tap] response=ser.read(4096) #4096 바이트의 데이타를 response에 넣습니다. 데이타를 잃어 버리는 부분이 response부분입니다.

[tap] data_buffer.append(response.hex())#data_buffer에 여러번의 response가 들어갑니다.
ser.close()
return data_buffer
data_buffer=[]

라스코니의 이미지

ser 에 대해서 flush를 하고 그 다음에 ser.read(4096)으로 읽어 오시는 군요. 그리고 그 데이터를 data_buffer에 append를 하시는군요.

그 사이에 ser 에 데이터가 들어오게 되면 어떻게 될까요? 다음 flush에 의해서 그 사이 들어온 데이터는 사라지게 될 것입니다. 물론 작은 확률이라서 매번 그렇게 되진 않겠지만 충분히 가능성이 있습니다.

보통 serial이나 네트웍으로 데이터를 시스템 버퍼에서 내부 버퍼로 옮길 때는 count = ser.read(MAX_LENGTH) 식으로 읽어서 최대 크기로 받기를 시도해 보고 실제 받은(읽은) count 개수만큼 처리해 주는 것이 일반적입니다. python은 잘 모르나 ser.read()가 실제로 넘겨준 개수를 return하는지 확인해 보시고, flush는 주석 처리해 보세요.

받은(읽은) 만큼만 append 해주면 됩니다. 정해진 길이만큼 처리할 필요 없습니다.

bushi의 이미지

ser.flushInput() 을 없애고 다시 해보세요.

nonblocking 으로 하고 계시는 것 같은데, 적어주신 코드로 보면 blocking 이어야 제대로 동작합니다.

millelove의 이미지

제가 이제서야 글을 확인했네요. 네네 buffer를 blocking으로 변경을 하였습니다. 그랬더니 동작을 하는듯합니다. 답변주셔셔 고맙습니다.

millelove의 이미지

답변주셔서 정말 고맙습니다. 지난주 금요일부터, 데이타를 잃어 버리는 문제때문에, 골머리를 앓았습니다. 하다가 하다가 안되어서, 웹사이트에 글까지 남겼습니다. 그리고 라스코니님의 조언대로 이래저래 해보았으나 데이타를 잃어 버리는 문제는 계속 발생하였습니다. 마침내 무엇이 원인인지 왜 데이타를 잃어 버리는지 알게 되었습니다.
제가 이해한바에 의하면, receiving buffer에는 두가지 종류가 있습니다. 하나는 blocking 다른 하나는 non-blocking입니다. blocking buffer같은 경우에는 데이타가 들어와야만 멈춥니다. 반만 non-blocking같은 경우에는 특정 시간을 기다린후, 데이타가 있거나 없거나 멈추는 경우입니다. 저는 non-blocking을 사용하여 모든걸 구현하였습니다. 그래서 data를 잃어 버리는 경우가 발생하였습니다. 그래서 data를 받을때는 blocking으로 변경한 후 테스트를 하였더니, 동작하는듯합니다. 좀 더 테스트를 해봐야겠지만, 되는듯 합니다.

라스코니의 이미지

음. non-blocking 방식은 read()를 호출했을 때 데이터가 들어와 있었던지 없었던지 그 상태를 가지고 바로 return하는 것입니다. 수신 버퍼에 데이터가 들어와 있었다면 사용자 버퍼에서 그만큼을 넘겨줄 것이고 수신 버퍼에 데이터가 들어와 있지 않다면 수신 카운트를 0으로 하고 리턴할 것입니다. 사용자가 봤을 때 read()를 호출해도 지연이 없기 때문에 non-blocking입니다.

blocking 은 read()를 호출했을 때 뭔가 들어올 때까지 리턴하지 않습니다. 이런 경우 read() 함수에서 멈추는 것처럼 보이기 때문에 blocking이라고 합니다.

만약 시리얼 low level device 드라이버를 직접 작성하고 있는 중이시라면 모르겠으나, python 모듈 기능을 통해 수신단을 개발하고 있는 중이라면 사용자가 non-blocking, blocking 을 선택할 수 없습니다. 그 모듈이 양쪽을 전부 지원하고 그 선택 기능을 제공하고 있지 않다면 말이죠. 즉 시리얼 디바이스 드라이버에 달렸습니다.

현재 되고 있는 코드를 다시 한번 올리시면 확인하는데 도움이 될 것 같습니다.

그리고 되도록 non-blocking 방식을 채택하는 것이 나중에 관리하기 좋습니다. thread model을 어떻게 가져가실지 모르나 blocking으로 했을 때 데이터가 들어오지 않는 경우가 생긴다면 최악의 경우 시스템이 먹통이 될 수도 있습니다.

보통 blocking 방식은 왔다 갔다하는 규칙이 완전히 정해진 설계에 쓰곤 합니다. 즉 hand shake 방식이죠. 그 규칙에서 벗어난 행동이 송신, 수신측에서 발생하게 되면 무한 루프, 즉 응답 없음으로 빠집니다.

bushi의 이미지

receive buffer 에 두 가지 종류가 있는 것이 아닙니다.

거의 모든 장치를 blocking 과 non blocking 으로 다룰 수 있는데,
시리얼 포트는 중간에 껴있는 tty 때문에 굉장히 독특한 특성이 더해집니다.
read 의 경우 blocking 을 해야 할 최소 바이트 수라던가, blocking 을 해야할 시간 이라던가를 드라이버(tty)에 직접 설정이 가능한 수준이고,
write 의 경우 kernel buffer 로의 복사 과정에 필요한 blocking 뿐 아니라 kernel buffer 에 있는 것들이 실제로 uart 장치를 통해 출력이 되는 과정까지의 blocking 도 별도로 수행할 수 있습니다.

pyserial 이 이 모든 복잡한 tty 를 그대로 보여주는 대신,
다른 일반적인 장치들을 다룰 때의 blocking/non-blocking 개념 정도만으로도 충분히 사용할 수 있을 정도의 api 를 제공하는 것으로 보입니다.
아마도 2.x 버전을 사용 중이신 것 같은데, pyserial API 정도만이라도 충분히 이해하고 사용하시는게 좋을 것 같습니다.
blocking/non-blocking 뿐만 아니라 flush 에 관한 것조차도 매뉴얼을 읽지않고 그냥 기분 내키는대로 사용하시는 것으로 생각됩니다.

millelove의 이미지

말씀하신대로, 메뉴얼을 읽어보고 일을 시작한게 아니예요. 여기에 많은 고수님들 덕분에 이번에 많이 배우고 있습니다. 다시 한번 더 감사합니다.

millelove의 이미지

실례가 안된다면 이메일 주소를 알 수 있을까요? 제가 짠 모든 코드 다 보내드릴께요. 제가 초보라서 저의 코드를 보시고 어떻게 코드를 짜면 더 좋은지도 말씀해주시면 좋겠어요. 지금은 동작하는것에만 집중해서 코드가 많이 지저분합니다. 다음번 일을 할때 라스코니님의 조언대로 코드를 짜도록 할께요.

그리고 드라이버는 CH340을 사용하고 있어요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.