RS232C
このページを参考にすれば、実用に耐える通信ソフトを作成できます。

RS232Cは何十年も前からある通信プロトコルです。昔は記憶媒体がフロッピーなどの低速デバイスだったので、データを取りこぼさないように 複数の制御線を使ってました。案外大変でした。今では伝送スピードに比べてPCの処理速度が速いのでTXD,RXDだけの結線で済みます。ラスベリーパイ ではデバッガー機能を実装し統合環境があります。デバッグのためのRS232Cはあまり出番がなくなってきたように思います。
tkinterで作った通信アプリはこれ↓

ラスベリーパイとPCをRS232Cで接続して通信をします。

結線の方法
ラスベリーパイ側        シリアルアダプタ
TXD(8番ピン)--------------RXD
RXD(10番ピン)-------------TXD
GND----------------------GND
これだけです。

設定
おなじみの設定画面です。

シリアルポートを有効にして終了
早速一番シンプルなコードでラズベリーパイとPCの通信コードを書いてみる。
PC側はTera Termを使います。

ラズベリーパイ→PC
送信データがPCに届かない

PC→ラズベリーパイ
文字化けしたデータがPCに表示される
ラズベリーパイに送信データが届かない

????わからない?
客観的な現状認識
PCからデータを送ると、ラズベリーパイから何やら不明のデータを送ってくるようだ。

ラズベリーパイが立ち上がりに何かしていないか?
Tera Termの画面を消去した状態で、ラズベリーパイを再起動。
Tera Termにこんな表示が!

ログイン画面です。現在のシリアルポートはログインの機能として割り当てられているんだなーーーーーー!ラズベリーパイの設定にはraspi-configというのがあります。シリアルポートの設定はこれでやるみたいなページがあったのでやってみる。もしかすると、
raspi-config != GUIの設定
みたいですね。

raspi-configの設定

sudo raspi-config

Interface Optionを選択し、[Tab]キーを押します。
[Enter]キーを押します。


これがキモの設定です。シリアルを介してログインするかを訊いています。
いいえ
を選択して[Enter]キーを押します。

はい
を選択して[Enter]キーを押します。

[Enter]キーを押します。
これで終わりです。

PCのTera Termを起動した状態で、ラズベリーパイを再起動します。
Tera Termにログイン画面が出ないことを確認してください。

シリアルに関してはラズベリーパイ4でいろいろ仕様変更されています。この記事はラズベリーパイ4を使っています。他のバージョンのラズベリーでは確認していません。

通信アプリの設計
以下のような画面にします。

①PCからの受信データをリアルタイムに表示する。
 文字はPCにエコーバックされる。
②左下のテキストボックスに入力した文字列は送信ボタンをクリックするとPCに送信する。
 左上のテキストボックスに文字列が表示される。
③緑と紫のテキストボックスはリードオンリーで編集できない。

ここで問題なのはPCがいつデータを送ってくるのかわからないので、常にデータを読み込む必要があります。具体的には、
rxData = g_Serial.read()
ここで受信するまで止まっています。
受診の処理と他の処理を両立させるためにスレッドを使います。 Pythonに限らず通信アプリにはスレッドは必須です。
ソースです。

# -*- coding: utf-8 -*-
import tkinter  as tk
import serial
import threading
import time

g_Serial = None

#RX232C受信スレッド関数
def rx232c():

    while(True):
        try:
            rxData = g_Serial.read()
            #PCにエコーバック
            g_Serial.write(rxData)
            txtRX.configure(state= tk.NORMAL)
            txtRX.insert(tk.END, rxData)
            txtRX.configure(state='disabled')
            #break
        except:
            break
    print("terminate thread")
    
            
#送信処理
def exec_send():
    t = txtEdit.get(1.0, tk.END)
    #改行が自動的に行末に挿入されている
    txtTX.configure(state= tk.NORMAL)
    txtTX.insert(tk.END, t)
    txtTX.configure(state='disabled')
    txtEdit.delete(1.0 , tk.END)
    #Tera Termの受信はLFがないのでLFCRの改行にする
    s = t.replace('\n', '\r\n')
 
    g_Serial.write(bytes(s, encoding='utf-8'))
    
def close_window():
    root.destroy() 


if __name__ == "__main__":
    root = tk.Tk()
    root.title('RS232C communication')
    root.geometry("520x300")

    #serial port setting
    g_Serial = serial.Serial('/dev/serial0', 115200)
    #スレッドの作成
    threadRx = threading.Thread(target=rx232c)
    #スレッドの開始 rx232cが受信の処理をする
    threadRx.start()

    lblTX = tk.Label(
        root, relief=tk.RIDGE, bd=2, text='Raspberry to PC')
    lblTX.place(x=2, y=2)

    lblTX = tk.Label(
        root, relief=tk.RIDGE, bd=2, text='PC to Raspberry')
    lblTX.place(x=260, y=2)

    # RX recieving strings
    txtRX = tk.Text(root, height=10, width=30, bg='#ccf', state=tk.DISABLED)
    txtRX.place(x = 260, y = 32)
    # TX sending strings
    txtTX = tk.Text(root, height=10, width=30, bg='#cfc', state=tk.DISABLED)
    txtTX.place(x = 2, y = 32)

    #edit box
    txtEdit = tk.Text(root, height=2, width=30, bg='#fff')
    txtEdit.place(x = 2, y = 230)
    
    #TX button
    button = tk.Button(root, text='送信', command=exec_send)
    button.place(x = 260, y = 230)

    #root.protocol("WM_DELETE_WINDOW", close_window)

    root.mainloop()

    # X ボタンで終了
    #受信をやめる
    g_Serial.cancel_read()
    #例外処理によりrx232cが終了する
    
    g_Serial.close()
    #スレッドの終了
    threadRx.join()
    print("終了")

 g_Serial = serial.Serial('/dev/serial0', 115200)
Serialの構築です。
ポート/dev/serial0が8番ピン、10番ピンに実装されています。

    #スレッドの作成
    threadRx = threading.Thread(target=rx232c)
    #スレッドの開始 rx232cが受信の処理をする
    threadRx.start()
コメントの通りです。rx232cは、

def rx232c():

    while(True):
        try:
            rxData = g_Serial.read()
            #PCにエコーバック
            g_Serial.write(rxData)
            txtRX.configure(state= tk.NORMAL)
            txtRX.insert(tk.END, rxData)
            txtRX.configure(state='disabled')
            #break
        except:
            break
    print("terminate thread")
無限ループです。例外処理が起きるとループを抜けて関数を終了します。txtRXは禁止になっています。 txtRXに書き込むときは通常にし、て書込み、禁止にします。

Xボタンでアプリを終了するときに、ポートのクローズ、スレッドの終了をします。 別にやらなくても問題はなかったのですが、他の言語ではやらないと悪いことが起きる経験があります。やることにします。
Xボタンでアプリを終了する場所は???調べても見つからない!よーーく考えてみると
root.mainloop()の後ではないか?コードを実装。

    root.mainloop()

    # X ボタンで終了
    #受信をやめる
    g_Serial.cancel_read()
    #例外処理によりrx232cが終了する
    
    g_Serial.close()
    #スレッドの終了
    threadRx.join()
    print("終了")
正解でした。シリアルのクローズとスレッドの終了ができました。
threadRx.join()
はrx232cが終了していないと正しく機能しないようです。例外処理によりrx232cの無限ループを抜けて関数を終了するようにします。これで後処理ができました。