Forum: Mikrocontroller und Digitale Elektronik RP2040 serielle Schnittstellen und CDC zeitgleich verwenden (C-SDK)


von Peter W. (petwey)


Lesenswert?

Hallo Forum,

Ich will einen Pi Pico als Kommunikationsmodul verwenden. Über CDC 
sollen Kommandos vom PC zum Pico geschickt werden und später auch 
entsprechende Antworten zurückkommen. An den beiden anderen seriellen 
Schnittstellen hängen zwei weitere Geräte (kabelgebundene Fernsteuerung, 
Motorregler).

Das ganze habe ich momentan nur soweit aufgebaut, dass am PC alle 
Schittstellen ankommen (1x CDC, 2xRS232).
Dem Pico habe ich ein entsprechendes Programm verpasst, dass wenn auf 
einer Schnittstelle Daten eintreffen, diese an die nächste gesendet 
werden. Ich kann also am PC von einer auf die andere Schnittstelle 
senden. Das klappt soweit wenn ich 3x putty aufmache.

Das ist aber nicht das reale Leben. Also habe ich ein Lazarus-Programm 
geschrieben mit dem ich alle drei Schnittstellen mit Zufallstexten und 
Timergesteuert in zufälligen Intervallen zw. 20 und 90 ms versorge. 
Allen drei Schnittstellen habe ich beigebracht ein Ende-Zeichen ~ zu 
senden, damit ich die Daten besser auswerten kann.

Am PC lausche ich auf das was an den drei Schnittstellen eintrifft und 
vergleiche es mit dem was ich gesendet habe. Dabei liefert mir der 
Eingang der CDC (USB) - Schnittstelle immer Fehler -> es werden immer 
wieder einige Zeichen verschluckt. Das tritt nur an dieser Schnittstelle 
auf. Das Problem scheint mir im Pico zu liegen, aber ich verstehe nicht 
wieso die Daten nicht vollständig vom Pico Empfangen bzw. Gesendet 
werden. Wenn ich die Sende-Timer der anderen beiden Schnittstellen 
deaktiviere tritt der Fehler nicht auf.

Meine Vermutung liegt darin, dass während ich den printf der 
CDC-Schnittstelle ausführe ein Interrupt auf einer der seriellen 
Schnittstellen die Übertragung stört.

Für mich ist eine saubere Kommunikation sehr wichtig. Bitte um Hilfe 
anbei der Code:
Controlbox.c:
1
#include <stdio.h>
2
#include <string.h>
3
#include "pico/stdlib.h"
4
#include "hardware/uart.h"
5
#include "hardware/irq.h"
6
#include "hardware/gpio.h"
7
8
// UART defines
9
// UART0
10
#define UART_ID0 uart0
11
#define BAUD_RATE0 115200  // 230400
12
// UART1
13
#define UART_ID1 uart1
14
#define BAUD_RATE1 115200  // 230400
15
16
// Pin-Connections for
17
// UART0
18
#define UART_TX_PIN0 0
19
#define UART_RX_PIN0 1
20
// UART1
21
#define UART_TX_PIN1 8
22
#define UART_RX_PIN1 9
23
24
// Datenstruktur für Befehle von HB -> ControlBox
25
struct __attribute__ ((__packed__)) HB_Cmd {
26
    char Cmd[1];
27
    char Buf[8];
28
};
29
union HB_InUnion {
30
    char In[10];
31
    struct HB_Cmd Cmb;
32
} HB_In;
33
uint8_t HB_InCnt = 0;
34
bool HB_InRd = false;
35
bool HB_InDo = false;
36
37
// RX interrupt handler für UART0
38
void on_uart_rx0() {
39
    // Solange Zeichen im Puffer sind -> Zeichen einlesen
40
    while (uart_is_readable(UART_ID0)) {
41
        uint8_t ch = uart_getc(UART_ID0);
42
        // Startzeichen / initiert den Befehlsempfang
43
        if (ch == '/') {
44
            HB_InCnt = 0;                  // Zeichen-Zähler ist 0
45
            strcpy(HB_In.In,"         ");  // Input-String ist LEER
46
            HB_InRd = false;              // Lesevorgang ist nicht fertig.
47
            HB_InDo = true;                // Daten einfügen
48
        } 
49
        // Endezeichen ~ beendet den Befehlsempfang
50
        else if (ch == '~') {  
51
            HB_InRd = true;               // Lesevorgang ist fertig      
52
            HB_InDo = false;               // Daten einfügen beendet      
53
        }
54
        else if (HB_InDo){
55
            HB_In.In[HB_InCnt++] = ch;
56
        }
57
    }
58
    // Verarbeiten des Kommandos wenn vollständig
59
    if (HB_InRd) {
60
        HB_InRd = false;                  // Flag für die Datenverarbeitung zurücksetzen.
61
        HB_In.In[HB_InCnt] = 0;            // Nullterminierung einfügen
62
        char Out[13];
63
        // Daten für Testausgabe vorbereiten und ...
64
        strcpy(Out, HB_In.In);
65
        strcat(Out, "~");
66
        // ... auf anderem Port ausgeben
67
        while (!uart_is_writable(UART_ID1)) {}
68
        uart_puts(UART_ID1, Out);
69
    }
70
}
71
72
// Datenstruktur für Feedbacks vom Motorregler -> ControlBox
73
struct __attribute__ ((__packed__)) MR_Cmd {
74
    char Cmd[1];
75
    char Buf[8];
76
};
77
union MR_InUnion {
78
    char In[13];
79
    struct HB_Cmd Cmb;
80
} MR_In;
81
uint8_t MR_InCnt = 0;
82
bool MR_InRd = false;
83
84
// RX interrupt handler für UART1
85
void on_uart_rx1() {
86
    // Solange Zeichen im Puffer sind -> Zeichen einlesen
87
    while (uart_is_readable(UART_ID1)) {
88
        uint8_t ch = uart_getc(UART_ID1);
89
        // Startzeichen gibt es hier nicht !!!!!
90
        if (ch == ';') {  
91
            MR_InRd = true;               // Lesevorgang ist fertig      
92
        }
93
        else {
94
            MR_In.In[MR_InCnt++] = ch;
95
            if (MR_InCnt > 13) {
96
                strcpy(MR_In.In,"            ");
97
                MR_InRd = false;
98
                MR_InCnt = 0;
99
            }
100
        }
101
    }
102
    // Verarbeiten des Kommandos wenn vollständig
103
    if (MR_InRd) {
104
        MR_InRd = false;                  // Flag für die Datenverarbeitung zurücksetzen.
105
        MR_In.In[MR_InCnt] = 0;            // Nullterminierung einfügen
106
        char Out[16];
107
        // Daten für Testausgabe vorbereiten und ...
108
        strcpy(Out, MR_In.In);
109
        strcat(Out, "~");
110
        // ... auf anderem Port ausgeben
111
        printf(Out);
112
        //puts(Out);
113
        strcpy(MR_In.In,"            ");
114
        MR_InCnt = 0;
115
    }
116
}
117
118
// GPIO defines
119
// Example uses GPIO 2
120
#define GPIO 2
121
122
123
// Datenstruktur für Befehle von PC -> ControlBox
124
struct __attribute__ ((__packed__)) PC_Cmd {
125
    char Cmd[1];
126
    char CRC[2];
127
    char Buf[16];
128
};
129
union PC_InUnion {
130
    char In[20];
131
    struct PC_Cmd Cmb;
132
} PC_In;
133
uint8_t PC_InCnt = 0;
134
bool PC_InRd = false;
135
bool PC_InDo = false;
136
137
138
// RX interrupt handler für VCOM
139
void on_uart_rx2() {
140
    // Liest schnellstmöglich ein Zeichen ein.
141
    int i = getchar_timeout_us (0);
142
    // Solange die Lese-Aktion keinen Timeout hat -> Zeichen verarbeiten
143
    while (i != PICO_ERROR_TIMEOUT) {
144
        uint8_t ch = i;
145
        // Startzeichen / initiert den Befehlsempfang
146
        if (ch == '/') {
147
            PC_InCnt = 0;                            // Zeichen-Zähler ist 0
148
            strcpy(PC_In.In,"                   "); // Input-String ist LEER
149
            PC_InRd = false;                        // Lesevorgang ist nicht fertig.
150
            PC_InDo = true;                          // Daten einfügen
151
        } 
152
        // Endezeichen ~ beendet den Befehlsempfang
153
        else if (ch == '~') {  
154
            PC_InRd = true;                         // Lesevorgang ist fertig      
155
            PC_InDo = false;                         // Daten einfügen beendet      
156
        }
157
        else if (PC_InDo){
158
            PC_In.In[PC_InCnt++] = ch;
159
        }
160
        i = getchar_timeout_us (0);                  // Nächstes Zeichen lesen
161
    }
162
    // Verarbeiten des Kommandos wenn vollständig
163
    if (PC_InRd) {
164
        PC_InRd = false;                            // Flag für die Datenverarbeitung zurücksetzen.
165
        PC_In.In[PC_InCnt] = 0;                      // Nullterminierung einfügen
166
        char Out[22];
167
        // Daten für Testausgabe vorbereiten und ...
168
        strcpy(Out, PC_In.In);
169
        strcat(Out, "~");
170
        // ... auf anderem Port ausgeben
171
        while (!uart_is_writable(UART_ID0)) {}
172
        uart_puts(UART_ID0, Out);
173
    }
174
}
175
176
const uint LED_PIN = PICO_DEFAULT_LED_PIN;
177
bool led_state = 0;
178
179
// Callback-Routine für den Repeating-timer
180
bool repeating_timer_callback(struct repeating_timer *t) 
181
{
182
    gpio_put(LED_PIN, led_state);
183
    led_state = !led_state;
184
    return true;
185
}
186
187
int main()
188
{
189
    gpio_init(LED_PIN);
190
    gpio_set_dir(LED_PIN, GPIO_OUT);
191
    
192
    stdio_init_all();
193
194
    // Set up our UART
195
    uart_init(UART_ID0, BAUD_RATE0);
196
    uart_init(UART_ID1, BAUD_RATE1);
197
    // Set the TX and RX pins by using the function select on the GPIO
198
    // Set datasheet for more information on function select
199
    gpio_set_function(UART_TX_PIN0, GPIO_FUNC_UART);
200
    gpio_set_function(UART_RX_PIN0, GPIO_FUNC_UART);
201
    gpio_set_function(UART_TX_PIN1, GPIO_FUNC_UART);
202
    gpio_set_function(UART_RX_PIN1, GPIO_FUNC_UART);
203
    
204
    // Turn off FIFO's - we want to do this character by character
205
    uart_set_fifo_enabled(UART_ID0, false);
206
    uart_set_fifo_enabled(UART_ID1, false);
207
208
    // Set up a RX interrupt
209
    // We need to set up the handler first
210
    // Select correct interrupt for the UART we are using
211
    int UART_IRQ0 = UART0_IRQ;
212
    int UART_IRQ1 = UART1_IRQ;
213
214
    // And set up and enable the interrupt handlers
215
    irq_set_exclusive_handler(UART_IRQ0, on_uart_rx0);
216
    irq_set_enabled(UART_IRQ0, true);
217
    irq_set_exclusive_handler(UART_IRQ1, on_uart_rx1);
218
    irq_set_enabled(UART_IRQ1, true);
219
220
    // Now enable the UART to send interrupts - RX only
221
    uart_set_irq_enables(UART_ID0, true, false);
222
    uart_set_irq_enables(UART_ID1, true, false);
223
224
    // GPIO initialisation.
225
    // We will make this GPIO an input, and pull it up by default
226
    gpio_init(GPIO);
227
    gpio_set_dir(GPIO, GPIO_IN);
228
    gpio_pull_up(GPIO);
229
    
230
    struct repeating_timer timer;
231
    bool rm_art = add_repeating_timer_ms(500, repeating_timer_callback, NULL, &timer);
232
233
    while (true) {
234
        on_uart_rx2();
235
    }
236
}

CMakeLists.txt:
1
# Generated Cmake Pico project file
2
3
cmake_minimum_required(VERSION 3.13)
4
5
set(CMAKE_C_STANDARD 11)
6
set(CMAKE_CXX_STANDARD 17)
7
8
# Initialise pico_sdk from installed location
9
# (note this can come from environment, CMake cache etc)
10
set(PICO_SDK_PATH "/home/peter/pico/pico-sdk")
11
12
set(PICO_BOARD pico CACHE STRING "Board type")
13
14
# Pull in Raspberry Pi Pico SDK (must be before project)
15
include(pico_sdk_import.cmake)
16
17
if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
18
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
19
endif()
20
21
project(ControlBox C CXX ASM)
22
23
# Initialise the Raspberry Pi Pico SDK
24
pico_sdk_init()
25
26
# Add executable. Default name is the project name, version 0.1
27
28
add_executable(ControlBox ControlBox.c )
29
30
pico_set_program_name(ControlBox "ControlBox")
31
pico_set_program_version(ControlBox "0.1")
32
33
pico_enable_stdio_uart(ControlBox 0)
34
pico_enable_stdio_usb(ControlBox 1)
35
36
# Add the standard library to the build
37
target_link_libraries(ControlBox
38
        pico_stdlib)
39
#        pico_multicore)
40
41
# Add the standard include files to the build
42
target_include_directories(ControlBox PRIVATE
43
  ${CMAKE_CURRENT_LIST_DIR}
44
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
45
)
46
47
# Add any user requested libraries
48
target_link_libraries(ControlBox 
49
        hardware_spi
50
        hardware_i2c
51
        hardware_pio
52
        )
53
54
pico_add_extra_outputs(ControlBox)

Pascal-Programm:
1
unit Unit1;
2
3
{$mode objfpc}{$H+}
4
5
interface
6
7
uses
8
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,
9
  SdpoSerial, LazSerial;
10
11
type
12
13
  { TForm1 }
14
15
  TForm1 = class(TForm)
16
    Button1: TButton;
17
    CheckBox1: TCheckBox;
18
    CheckBox2: TCheckBox;
19
    Label1: TLabel;
20
    Memo1: TMemo;
21
    Memo2: TMemo;
22
    PC: TSdpoSerial;
23
    HB: TSdpoSerial;
24
    MR: TSdpoSerial;
25
    THB: TTimer;
26
    TMR: TTimer;
27
    TPC: TTimer;
28
    procedure Button1Click(Sender: TObject);
29
    procedure CheckBox1Change(Sender: TObject);
30
    procedure CheckBox2Change(Sender: TObject);
31
    procedure FormCreate(Sender: TObject);
32
    procedure HBRxData(Sender: TObject);
33
    procedure MRRxData(Sender: TObject);
34
    procedure PCRxData(Sender: TObject);
35
    procedure THBTimer(Sender: TObject);
36
    procedure TMRTimer(Sender: TObject);
37
    procedure TPCTimer(Sender: TObject);
38
  private
39
    PC_Buf, HB_Buf, MR_Buf : string;
40
    PC_Snd, HB_Snd, MR_Snd : string;
41
    RecvCnt : Integer;
42
    function RandStr(Min, Max : Integer) : string;
43
  public
44
45
  end;
46
47
var
48
  Form1: TForm1;
49
50
implementation
51
52
uses
53
  Math;
54
55
{$R *.lfm}
56
57
{ TForm1 }
58
59
function TForm1.RandStr(Min, Max : Integer) : string;
60
Var
61
  i, j : Integer;
62
begin
63
  j := RandomRange(Min, Max);
64
  result := '';
65
  for i := 1 to j do
66
    result := result + Chr(RandomRange(65,90))
67
end;
68
69
procedure TForm1.Button1Click(Sender: TObject);
70
begin
71
  PC_Snd := RandStr(3,16)+'~';
72
  HB_Snd := RandStr(3,12)+'~';
73
  MR_Snd := RandStr(3,10);
74
  Memo1.Lines.Add('PC_Snd: '+PC_Snd);
75
  Memo1.Lines.Add('HB_Snd: '+HB_Snd);
76
  PC.WriteData('/'+PC_Snd);
77
  HB.WriteData('/'+HB_Snd);
78
  MR.WriteData(MR_Snd+';');
79
  MR_Snd := MR_Snd + '~';
80
  Memo1.Lines.Add('MR_Snd: '+MR_Snd);
81
end;
82
83
procedure TForm1.CheckBox1Change(Sender: TObject);
84
begin
85
  PC.Active := CheckBox1.Checked;
86
  HB.Active := CheckBox1.Checked;
87
  MR.Active := CheckBox1.Checked;
88
end;
89
90
procedure TForm1.CheckBox2Change(Sender: TObject);
91
begin
92
  Memo1.Lines.Add('Start/Stopp: '+FloatToStr(Now));
93
  TPC.Enabled := CheckBox2.Checked;
94
  THB.Enabled := CheckBox2.Checked;
95
  TMR.Enabled := CheckBox2.Checked;
96
end;
97
98
procedure TForm1.FormCreate(Sender: TObject);
99
begin
100
  Randomize;
101
  PC_Buf := '';
102
  HB_Buf := '';
103
  MR_Buf := '';
104
  RecvCnt := 0;
105
end;
106
107
procedure TForm1.HBRxData(Sender: TObject);
108
Var
109
  s : string;
110
begin
111
  HB_Buf := HB_Buf + HB.ReadData;
112
  if Pos('~',HB_Buf) <> 0 then
113
  begin
114
    inc(RecvCnt);
115
    //Memo1.Lines.Add('HB: '+HB_Buf);
116
    if PC_Snd <> HB_Buf then
117
    begin
118
      s := FloatToStr(Now) + ': PC (' + PC_Snd + ') <> HB (' + HB_Buf + ')';
119
      Memo1.Lines.Add(s);
120
    end;
121
    HB_Buf := '';
122
    label1.Caption := IntToStr(RecvCnt);
123
  end;
124
end;
125
126
procedure TForm1.MRRxData(Sender: TObject);
127
Var
128
  s : string;
129
begin
130
  MR_Buf := MR_Buf + MR.ReadData;
131
  if Pos('~',MR_Buf) <> 0 then
132
  begin
133
    inc(RecvCnt);
134
    //Memo1.Lines.Add('MR: '+MR_Buf);
135
    if HB_Snd <> MR_Buf then
136
    begin
137
      s := FloatToStr(Now) + ': HB (' + HB_Snd + ') <> MR (' + MR_Buf + ')';
138
      Memo1.Lines.Add(s);
139
    end;
140
    MR_Buf := '';
141
    label1.Caption := IntToStr(RecvCnt);
142
  end;
143
end;
144
145
procedure TForm1.PCRxData(Sender: TObject);
146
Var
147
  s : string;
148
begin
149
  s := PC.ReadData;
150
  Memo2.Lines.Add('Recv ' + s);
151
  PC_Buf := PC_Buf + s;
152
  if Pos('~',PC_Buf) <> 0 then
153
  begin
154
    inc(RecvCnt);
155
    //Memo1.Lines.Add('PC: '+PC_Buf);
156
    if MR_Snd <> PC_Buf then
157
    begin
158
      s := FloatToStr(Now) + ' MR (' + MR_Snd + ') <> PC (' + PC_Buf + ')';
159
      Memo1.Lines.Add(s);
160
    end;
161
    PC_Buf := '';
162
    label1.Caption := IntToStr(RecvCnt);
163
  end;
164
end;
165
166
procedure TForm1.THBTimer(Sender: TObject);
167
begin
168
  THB.Enabled := False;
169
  HB_Snd := RandStr(3,12)+'~';
170
  HB.WriteData('/'+HB_Snd);
171
  THB.Interval :=  RandomRange(20,90);
172
  THB.Enabled := True;
173
end;
174
175
procedure TForm1.TMRTimer(Sender: TObject);
176
begin
177
  TMR.Enabled := False;
178
  MR_Snd := RandStr(3,9);
179
  //MR_Snd := CalcCRC8(MR_Snd) + MR_Snd;
180
  MR.WriteData(MR_Snd+';');
181
  MR_Snd := MR_Snd + '~';
182
  Memo2.Lines.Add('Send ' + MR_Snd);
183
  TMR.Interval :=  RandomRange(20,90);
184
  TMR.Enabled := True;
185
end;
186
187
procedure TForm1.TPCTimer(Sender: TObject);
188
begin
189
  TPC.Enabled := False;
190
  PC_Snd := RandStr(3,16)+'~';
191
  PC.WriteData('/'+PC_Snd);
192
  TPC.Interval :=  RandomRange(20,90);
193
  TPC.Enabled := True;
194
end;
195
196
end.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.