Forum: Mikrocontroller und Digitale Elektronik FreeRTOS - ESP8266 "vergisst" Bytes beim Senden über SPI


von Max M. (maxmicr)


Angehängte Dateien:

Lesenswert?

Der ESP8266 soll zwei einfache Nachrichten per SPI versenden (als 
Master) und anschließend 1 Byte empfangen, der gesamte Code sieht so 
aus:
1
bool isLEDon = false;
2
3
void send_spi(uint8_t *data, uint8_t length) {
4
  spi_trans_t trans;
5
  memset(&trans, 0, sizeof(trans));
6
  
7
  trans.bits.mosi = length * 8;
8
  trans.mosi = data;
9
  trans.addr = NULL;
10
  trans.cmd = NULL;
11
12
  spi_trans(HSPI_HOST, &trans);
13
}
14
15
uint8_t read_spi() {
16
  uint8_t resp[1];
17
18
  spi_trans_t trans;
19
  memset(&trans, 0, sizeof(trans));
20
  
21
  trans.bits.miso = 8;
22
  trans.miso = resp;
23
  trans.addr = NULL;
24
  trans.cmd = NULL;
25
  
26
  spi_trans(HSPI_HOST, &trans);
27
  
28
  return resp[0];
29
}
30
31
void ICACHE_FLASH_ATTR spi_master_write_slave_task(void *arg) {
32
  printf("SPI Write-Task started...\n");
33
  const uint32_t addr = 5829;
34
  const uint8_t addrMSB =  (addr >> 16);
35
  const uint8_t addrMLSB = (addr >> 8);
36
  const uint8_t addrLSB = (addr & 0xFF);
37
  
38
  uint8_t readMsg[] = {
39
    READ_CMD, addrMSB, addrMLSB, addrLSB
40
  };
41
  
42
  uint8_t writeMsg[] = {
43
    WRITE_CMD, addrMSB, addrMLSB, addrLSB, 133
44
  };
45
46
  while(true) {
47
    send_spi(writeMsg, sizeof(writeMsg));
48
49
    vTaskDelay(10 / portTICK_PERIOD_MS);
50
51
    send_spi(readMsg, sizeof(readMsg));
52
    
53
    vTaskDelay(25 / portTICK_PERIOD_MS);
54
    
55
    const uint8_t resp = read_spi();
56
    
57
    printf("RESPONSE: %d\n", resp);
58
59
    if(isLEDon) {
60
      gpio_set_level(GPIO_NUM_2, 1);
61
      isLEDon = false;
62
    } else {
63
      gpio_set_level(GPIO_NUM_2, 0);
64
      isLEDon = true;
65
    }
66
67
    vTaskDelay(100 / portTICK_PERIOD_MS);
68
  }
69
}
70
71
void ICACHE_FLASH_ATTR app_main() {
72
  gpio_config_t io_conf;
73
  io_conf.mode = GPIO_MODE_OUTPUT;
74
  io_conf.pin_bit_mask = LED_OUTPUT_PIN_SEL;
75
  io_conf.pull_down_en = 0;
76
  io_conf.pull_up_en = 0;
77
  gpio_config(&io_conf);
78
  
79
  spi_config_t spi_config;
80
  spi_config.interface.val = SPI_DEFAULT_INTERFACE;
81
  spi_config.mode = SPI_MASTER_MODE;
82
  spi_config.clk_div = SPI_2MHz_DIV;
83
  spi_config.event_cb = NULL;
84
  spi_init(HSPI_HOST, &spi_config);
85
      
86
  xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave", 2048, NULL, 3, NULL);
87
}

Die 1. Nachricht ("Write") sieht perfekt aus, bei der 2. Nachricht 
vergisst der ESP8266 aber einfach die restlichen 3 Bytes und schickt nur 
das 1. Byte korrekt - warum?
Ich gönne ihm sogar extra 25ms Pause :(

P.S: Wenn ich die 1. Nachricht (also "Write") weglasse, dann wird die 
"Read"-Nachricht korrekt ausgegeben.

: Bearbeitet durch User
von minifloat (Gast)


Lesenswert?

Erstens
Max M. schrieb:
> void send_spi(uint8_t *data, uint8_t length) {
>   spi_trans_t trans;

Damit legst du das Objekt bzw. die Struktur trans funktionslokal an. Es 
liegt auf dem Stack. Wer garantiert dir nach Ende der Funktion, dass es 
noch da ist?

Zweitens
Max M. schrieb:
> trans.mosi = data;
Der Datenzeiger muss für die gesamte Zeit, bis alle Daten raus gepustet 
wurden, valide sein. Ist er das auch?

Drittens

Max M. schrieb:
> Ich gönne ihm sogar extra 25ms Pause
Warten ist keine Synchronisationsmethode

mfg mf

von Max M. (maxmicr)


Lesenswert?

minifloat schrieb:
> Wer garantiert dir nach Ende der Funktion, dass es
> noch da ist?

Wenn ich wüsste, wann das Übertragen abgeschlossen ist, könnte ich dir 
das sagen.

minifloat schrieb:
> Ist er das auch?

Gleiche Aussage wie oben. Ich hab keine Infos dazu gefunden, wann die 
SPI-Daten tatsächlich übertragen werden.


Danke für den Hinweis, ich werde die Variablen mal innerhalb der 
Task-Funktion global machen.

Edit: Leider nein, gleiches Verhalten (außer ich hab was übersehen):
1
void send_spi(spi_trans_t *trans, uint8_t *data, uint8_t length) {
2
  memset(trans, 0, sizeof(*trans));
3
  
4
  trans->bits.mosi = 8 * length;
5
  trans->mosi = data;
6
  trans->addr = NULL;
7
  trans->cmd = NULL;
8
9
  spi_trans(HSPI_HOST, trans);
10
}
11
12
void read_spi(spi_trans_t *trans, uint8_t *dest) {
13
  memset(trans, 0, sizeof(*trans));
14
  
15
  trans->bits.miso = 8;
16
  trans->miso = dest;
17
  trans->addr = NULL;
18
  trans->cmd = NULL;
19
  
20
  spi_trans(HSPI_HOST, trans);
21
}
22
23
typedef enum STATE {
24
  IDLE = 0,
25
  SENT_WRITE = 1,
26
  SENT_READ = 2
27
} STATE;
28
29
void ICACHE_FLASH_ATTR spi_master_write_slave_task(void *arg) {
30
  printf("SPI Write-Task started...\n");
31
  STATE state = IDLE;
32
33
  const uint32_t addr = 5829;
34
  const uint8_t addrMSB =  (addr >> 16);
35
  const uint8_t addrMLSB = (addr >> 8);
36
  const uint8_t addrLSB = (addr & 0xFF);
37
  
38
  uint8_t readMsg[] = {
39
    READ_CMD, addrMSB, addrMLSB, addrLSB
40
  };
41
  
42
  uint8_t writeMsg[] = {
43
    WRITE_CMD, addrMSB, addrMLSB, addrLSB, 133
44
  };
45
  
46
  uint8_t recvBuff[1];
47
  
48
  spi_trans_t trans;
49
50
  while(true) {
51
52
    switch(state) {
53
      case IDLE:
54
        send_spi(&trans, writeMsg, 5);
55
        state = SENT_WRITE;
56
      break;
57
      case SENT_WRITE:
58
        send_spi(&trans, readMsg, 4);
59
        state = SENT_READ;
60
      break;
61
      default:
62
        ;
63
        read_spi(&trans, recvBuff);
64
        printf("RESPONSE: %d\n", recvBuff[0]);
65
        state = IDLE;
66
      break;
67
    }
68
69
    if(isLEDon) {
70
      gpio_set_level(GPIO_NUM_2, 1);
71
    } else {
72
      gpio_set_level(GPIO_NUM_2, 0);
73
    }
74
    isLEDon = !isLEDon;
75
76
    vTaskDelay(10 / portTICK_PERIOD_MS);
77
  }
78
}

minifloat schrieb:
> Warten ist keine Synchronisationsmethode

Korrekt. Bei der wenigen Dokumentation, die es zum RTOS gibt, hab ich 
leider noch nicht rausgefunden, wie man einen SPI-Receive oder 
SPI-Transmit Interrupt implementiert.

: Bearbeitet durch User
von minifloat (Gast)


Lesenswert?

Max M. schrieb:
> ich werde die Variablen mal innerhalb der Task-Funktion global machen

Mach' die mal in der C-Source überhaupt global. Oderststic. Weil wenn 
das Ding auf dem Stack liegt, die Übertragung aber vermutlich 
Interrupt-getrieben  und nicht synchron zum Tasksystem, muss das Ding 
dauerhaft existent sein.

Max M. schrieb:
> Wenn ich wüsste, wann das Übertragen abgeschlossen ist,

Du hast dort eine wunderschöne Statemachine. Gibt es für das spi_trans_t 
Objekt keine IsReady-Funktion?
Gibt es Doku zu dem SPI-Krempel?

mfg mf

von minifloat (Gast)


Lesenswert?

minifloat schrieb:
> Oderststic

"Oder static"

von Max M. (maxmicr)


Angehängte Dateien:

Lesenswert?

minifloat schrieb:
> Mach' die mal in der C-Source überhaupt global.

So?
1
#include <stdio.h>
2
#include <string.h>
3
4
#include "freertos/FreeRTOS.h"
5
#include "freertos/task.h"
6
7
#include "esp_system.h"
8
#include "esp_spi_flash.h"
9
#include "esp_log.h"
10
11
#include "driver/gpio.h"
12
#include "driver/spi.h"
13
14
#define  LED_OUTPUT_PIN_SEL  ( (1 << GPIO_NUM_2) | (1 << GPIO_NUM_2) )
15
16
#define READ_CMD      0x13
17
#define WRITE_CMD      0x12
18
19
#define READ_ADDR      ( (uint32_t)5829 )
20
21
bool isLEDon = false;
22
23
static const uint8_t addrMSB =  (READ_ADDR >> 16);
24
static const uint8_t addrMLSB = (READ_ADDR >> 8);
25
static const uint8_t addrLSB = (READ_ADDR & 0xFF);
26
27
static uint8_t readMsg[4];
28
static uint8_t writeMsg[5];
29
30
static uint8_t recvBuff[1];
31
32
static spi_trans_t trans;
33
34
void send_spi(spi_trans_t *trans, uint8_t *data, uint8_t length) {
35
  memset(trans, 0, sizeof(*trans));
36
  
37
  trans->bits.mosi = 8 * length;
38
  trans->mosi = data;
39
  trans->addr = NULL;
40
  trans->cmd = NULL;
41
42
  spi_trans(HSPI_HOST, trans);
43
}
44
45
void read_spi(spi_trans_t *trans, uint8_t *dest) {
46
  memset(trans, 0, sizeof(*trans));
47
  
48
  trans->bits.miso = 8;
49
  trans->miso = dest;
50
  trans->addr = NULL;
51
  trans->cmd = NULL;
52
  
53
  spi_trans(HSPI_HOST, trans);
54
}
55
56
typedef enum STATE {
57
  IDLE = 0,
58
  SENT_WRITE = 1,
59
  SENT_READ = 2
60
} STATE;
61
62
void ICACHE_FLASH_ATTR spi_master_write_slave_task(void *arg) {
63
  printf("SPI Write-Task started...\n");
64
  STATE state = IDLE;
65
66
  while(true) {
67
68
    switch(state) {
69
      case IDLE:
70
        send_spi(&trans, writeMsg, 5);
71
        state = SENT_WRITE;
72
      break;
73
      case SENT_WRITE:
74
        send_spi(&trans, readMsg, 4);
75
        state = SENT_READ;
76
      break;
77
      default:
78
        ;
79
        read_spi(&trans, recvBuff);
80
        printf("RESPONSE: %d\n", recvBuff[0]);
81
        state = IDLE;
82
      break;
83
    }
84
85
    if(isLEDon) {
86
      gpio_set_level(GPIO_NUM_2, 1);
87
    } else {
88
      gpio_set_level(GPIO_NUM_2, 0);
89
    }
90
    isLEDon = !isLEDon;
91
92
    vTaskDelay(10 / portTICK_PERIOD_MS);
93
  }
94
}
95
96
void ICACHE_FLASH_ATTR app_main() {
97
  readMsg[0] = READ_CMD;
98
  readMsg[1] = addrMSB;
99
  readMsg[2] = addrMLSB;
100
  readMsg[3] = addrLSB;
101
  
102
  writeMsg[0] = WRITE_CMD;
103
  writeMsg[1] = addrMSB;
104
  writeMsg[2] = addrMLSB;
105
  writeMsg[3] = addrLSB;
106
  writeMsg[4] = 133;
107
  
108
  gpio_config_t io_conf;
109
  io_conf.mode = GPIO_MODE_OUTPUT;
110
  io_conf.pin_bit_mask = LED_OUTPUT_PIN_SEL;
111
  io_conf.pull_down_en = 0;
112
  io_conf.pull_up_en = 0;
113
  gpio_config(&io_conf);
114
  
115
  spi_config_t spi_config;
116
  spi_config.interface.val = SPI_DEFAULT_INTERFACE;
117
  spi_config.mode = SPI_MASTER_MODE;
118
  spi_config.clk_div = SPI_2MHz_DIV;
119
  spi_config.event_cb = NULL;
120
  spi_init(HSPI_HOST, &spi_config);
121
  
122
123
    /* Print chip information */
124
    esp_chip_info_t chip_info;
125
    esp_chip_info(&chip_info);
126
    printf("This is ESP8266 chip with %d CPU cores, WiFi, ",
127
            chip_info.cores);
128
129
    printf("silicon revision %d, ", chip_info.revision);
130
131
    printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
132
            (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
133
      
134
  xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave", 2048, NULL, 3, NULL);
135
}

Nun sind beide Nachrichten falsch

minifloat schrieb:
> Gibt es Doku zu dem SPI-Krempel?

https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/api-reference/peripherals/spi.html

: Bearbeitet durch User
von minifloat (Gast)


Lesenswert?

Max M. schrieb:
> Nun sind beide Nachrichten falsch

Soll ich ehrlich sein, das hatte ich bereits erwartet...
spi_trans() scheint für den Usercode kein "blockierender"Syscall zu 
sein. D.h. der Task wird nicht suspended, wenn SPI noch am Werk ist.

Max M. schrieb:
> minifloat schrieb:
>> Gibt es Doku zu dem SPI-Krempel?
>
> 
https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/api-reference/peripherals/spi.html

Schön. Hab' mal kurz durchgesehen. Es gibt offenbar keinen global 
abrufbaren Status der beiden SPIs. Spinlocks sind auch nicht das beste 
Mittel...
Aaaaaber. Du kannst einen Event-Callback anlegen. Das geht wohl im 
spi_config_t-Objekt:
>> spi_event_callback_t event_cb
>> SPI interrupt event callback

Und der Typ...
>> typedef void (*spi_event_callback_t)
>> (int event, void *arg)
...stellt in Aussicht, bei "int event" z.B. einen dieser hier zu 
bekommen...
>> SPI_TRANS_DONE_EVENT
>> SPI_TRANS_DONE

Also
a) Thread nach spi_trans() schlafen legen und mit dem Callback bei 
Erfolg wieder Aufwecken
b) Eigenen globalen Status mit dem Callback steuern.

mfg mf

von minifloat (Gast)


Lesenswert?

PS
> So?
Bassd scho'

Zum verborgenen "Warum?" hier
> Nun sind beide Nachrichten falsch
Du manipulierst die Nachricht, die noch gesendet werden soll...

von Max M. (maxmicr)


Lesenswert?

minifloat schrieb:
> Du kannst einen Event-Callback anlegen. Das geht wohl im
> spi_config_t-Objekt:

Danke für den Hinweis, ich habs nun mal versucht zu implementieren:
1
TaskHandle_t master_write_task;
2
/* ... */
3
4
void ICACHE_FLASH_ATTR spi_master_write_slave_task(void *arg) {
5
  printf("SPI Write-Task started...\n");
6
  const uint32_t addr = 5829;
7
  const uint8_t addrMSB =  (addr >> 16);
8
  const uint8_t addrMLSB = (addr >> 8);
9
  const uint8_t addrLSB = (addr & 0xFF);
10
  
11
  uint8_t readMsg[] = {
12
    READ_CMD, addrMSB, addrMLSB, addrLSB
13
  };
14
  
15
  uint8_t writeMsg[] = {
16
    WRITE_CMD, addrMSB, addrMLSB, addrLSB, 133
17
  };
18
19
  while(true) {
20
    send_spi(writeMsg, sizeof(writeMsg));
21
22
    vTaskSuspend(master_write_task);
23
24
    send_spi(readMsg, sizeof(readMsg));
25
    
26
    vTaskSuspend(master_write_task);
27
    
28
    const uint8_t resp = read_spi();
29
    /* ... */
30
  }
31
}
32
33
void spi_callback(int event, void *arg) {
34
  printf("SPI_INIT_EVENT: %d\n", SPI_INIT_EVENT);
35
  printf("SPI_TRANS_START_EVENT: %d\n", SPI_TRANS_START_EVENT);
36
  printf("SPI CALLBACK called with EVENT = %d, but SPI_TRANS_DONE=%d\n", event, SPI_TRANS_DONE_EVENT);
37
  if(event == SPI_TRANS_DONE_EVENT) {
38
    vTaskResume(master_write_task);
39
  }
40
}
41
42
void ICACHE_FLASH_ATTR app_main() {
43
  gpio_config_t io_conf;
44
  io_conf.mode = GPIO_MODE_OUTPUT;
45
  io_conf.pin_bit_mask = LED_OUTPUT_PIN_SEL;
46
  io_conf.pull_down_en = 0;
47
  io_conf.pull_up_en = 0;
48
  gpio_config(&io_conf);
49
  
50
  spi_config_t spi_config;
51
  spi_config.interface.val = SPI_DEFAULT_INTERFACE;
52
  spi_config.mode = SPI_MASTER_MODE;
53
  spi_config.clk_div = SPI_2MHz_DIV;
54
  spi_config.event_cb = &spi_callback;
55
  spi_init(HSPI_HOST, &spi_config);
56
      
57
  xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave", 2048, NULL, 3, &master_write_task);
58
}

Dann stürzt der ESP8266 aber dauernd ab:
1
[0;32mI (617) reset_reason: RTC reset 2 wakeup 0 store 4, reason is 2[0m
2
[0;32mI (623) gpio: GPIO[2]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:1074812240 [0m
3
[0;31mE (638) gpio: gpio_set_intr_type(138): GPIO interrupt type error[0m
4
SPI_INIT_EVENT: 0
5
SPI_TRANS_START_EVENT: 1
6
SPI CALLBACK called with EVENT = 0, but SPI_TRANS_DONE=2
7
SPI Write-Task started...
8
SPI_INIT_EVENT: 0
9
SPI_TRANS_START_EVENT: 1
10
SPI CALLBACK called with EVENT = 1, but SPI_TRANS_DONE=2
11
abort() was called at PC 0x402189fb on core 0
12
13
Guru Meditation Error: Core  0 panic'ed (StoreProhibited). Exception was unhandled.
14
15
Core 0 register dump:
16
PC      : 0x40218845  PS      : 0x00000033  A0      : 0x40218840  A1      : 0x3ffe8110  
17
18
A2      : 0x00000000  A3      : 0x00000000  A4      : 0x00000000  A5      : 0x00000001  
19
20
A6      : 0x00000001  A7      : 0x40229464  A8      : 0x00000040  A9      : 0x00000001  
21
22
A10     : 0x0000002d  A11     : 0x00000040  A12     : 0x3ffeb1f0  A13     : 0xffffffff  
23
24
A14     : 0x00000004  A15     : 0x401058b8  SAR     : 0x0000001e  EXCCAUSE: 0x0000001d

Vielleicht sollte Espressif lieber mehr Doku schreiben anstatt auf 
GitHub jeden Tag Commits rauszuhauen.

Edit: Sobald ich alle *printf()*-Ausgaben aus der spi_callback 
entferne, stürzt der ESP8266 nicht mehr ab (öhm alles klar!), allerdings 
wird der Task trotzdem nicht wieder aufgenommen (eventuell da das Event 
nie aufgerufen wird) - die 1. Nachricht läuft aber richtig über das SPI.

: Bearbeitet durch User
von minifloat (Gast)


Lesenswert?

Was machst du denn da?
Max M. schrieb:
> const uint8_t resp = read_spi();

Weil das passt irgendwie nicht so recht zu deiner Deklaration...
Max M. schrieb:
> void read_spi(spi_trans_t *trans, uint8_t *dest)
...als void-Funktion und dann willst du einen const uint8_t beschreiben.

Würde irgendwie zu...
Max M. schrieb:
> Guru Meditation Error: Core  0 panic'ed (StoreProhibited).
...passen.
Ich geh pennen.
mfg mf

von minifloat (Gast)


Lesenswert?

Max M. schrieb:
> Edit: Sobald ich alle *printf()*-Ausgaben aus der spi_callback entferne,
> stürzt der ESP8266 nicht mehr ab (öhm alles klar!),

Aha. Ist printf vielleicht ein blocking system call (Gift im IRQ) 
und/oder schreibt irgendwo wo er nicht soll?

Ich hab da eine Frage für Dumme.
Warum heißt es xTaskCreate
Aber vTaskSuspend und vTaskResume?

btw. Ich hab' keine Erfahrung mit FreeRTOS, alles nur höngengebliebenes 
Wissen aus dem Studium, was man eben über RTOS so vor 5-7 Jahren gelehrt 
hat.

Aus dem Callback heraus sollte es aber möglich sein, Tasks aufzuwecken. 
Wäre ja sonst sinnlos.

Letzte Idee des Abends
Dass es ein SPI-Init-Event gibt, sagt mir dass du vielleicht vor dem SPI 
losballern erst auf dieses SPI-Init-Event warten solltest. Damit würde 
ich dann den Task zum losballern starten.

n8 mfg mf

von Max M. (maxmicr)


Lesenswert?

minifloat schrieb:
> Was machst du denn da?

Ups, da hab ich zwei Codestände durcheinander geschmissen.

minifloat schrieb:
> Dass es ein SPI-Init-Event gibt, sagt mir dass du vielleicht vor dem SPI
> losballern erst auf dieses SPI-Init-Event warten solltest. Damit würde
> ich dann den Task zum losballern starten.

Hab ich nun probiert:
1
#include <stdio.h>
2
#include <string.h>
3
4
#include "freertos/FreeRTOS.h"
5
#include "freertos/semphr.h"
6
#include "freertos/task.h"
7
8
#include "esp_system.h"
9
#include "esp_spi_flash.h"
10
#include "esp_log.h"
11
12
#include "driver/gpio.h"
13
#include "driver/spi.h"
14
15
#define  LED_OUTPUT_PIN_SEL  (1 << GPIO_NUM_2)
16
17
#define READ_CMD      0x13
18
#define WRITE_CMD      0x12
19
20
bool isLEDon = false;
21
SemaphoreHandle_t mutex;
22
TaskHandle_t spi_task_handle;
23
24
void send_spi(spi_trans_t *trans, uint8_t *data, uint8_t length) {
25
  memset(trans, 0, sizeof(*trans));
26
  
27
  trans->bits.mosi = length * 8;
28
  trans->mosi = data;
29
  trans->addr = NULL;
30
  trans->cmd = NULL;
31
32
  spi_trans(HSPI_HOST, trans);
33
}
34
35
void read_spi(spi_trans_t *trans, uint8_t *buf) {
36
  memset(trans, 0, sizeof(*trans));
37
  
38
  trans->bits.miso = 8;
39
  trans->miso = buf;
40
  trans->addr = NULL;
41
  trans->cmd = NULL;
42
  
43
  spi_trans(HSPI_HOST, trans);
44
}
45
46
uint8_t readMsgBuf[4];
47
uint8_t writeMsgBuf[5];
48
uint8_t recvBuf[1];
49
spi_trans_t trans;
50
51
typedef enum STATE {
52
  IDLE = 0,
53
  SENT_WRITE = 1,
54
  SENT_READ = 2
55
} STATE;
56
57
void ICACHE_FLASH_ATTR spi_master_write_slave_task(void *arg) {
58
  printf("SPI Write-Task started...\n");
59
  STATE state = IDLE;
60
61
  const uint32_t addr = 5829;
62
  const uint8_t addrMSB =  (addr >> 16);
63
  const uint8_t addrMLSB = (addr >> 8);
64
  const uint8_t addrLSB = (addr & 0xFF);
65
66
  readMsgBuf[0] = READ_CMD;
67
  readMsgBuf[1] = addrMSB;
68
  readMsgBuf[2] = addrMLSB;
69
  readMsgBuf[3] = addrLSB;
70
71
  writeMsgBuf[0] = WRITE_CMD;
72
  writeMsgBuf[1] = addrMSB;
73
  writeMsgBuf[2] = addrMLSB;
74
  writeMsgBuf[3] = addrLSB;
75
  writeMsgBuf[4] = 133;
76
77
  while(true) {
78
    switch(state) {
79
      case IDLE: {
80
        send_spi(&trans, writeMsgBuf, sizeof(writeMsgBuf));
81
82
        xSemaphoreTake(mutex, portMAX_DELAY);
83
        //vTaskSuspend(spi_task_handle);
84
85
        state = SENT_WRITE;
86
      }
87
      break;
88
89
      case SENT_WRITE: {
90
        send_spi(&trans, readMsgBuf, sizeof(readMsgBuf));
91
92
        xSemaphoreTake(mutex, portMAX_DELAY);
93
        //vTaskSuspend(spi_task_handle);
94
95
        state = SENT_READ;
96
      }
97
      break;
98
99
      case SENT_READ: {
100
        read_spi(&trans, recvBuf);
101
102
        state = IDLE;
103
      }
104
      break;
105
  }
106
107
  if(isLEDon) {
108
    gpio_set_level(GPIO_NUM_2, 1);
109
    isLEDon = false;
110
  } else {
111
    gpio_set_level(GPIO_NUM_2, 0);
112
    isLEDon = true;
113
  }
114
115
  vTaskDelay(100 / portTICK_PERIOD_MS);
116
  }
117
}
118
119
void spi_callback(int event, void *arg) {
120
    switch (event) {
121
        case SPI_INIT_EVENT: {
122
      xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave", 2048, NULL, 3, &spi_task_handle);
123
        }
124
        break;
125
126
        case SPI_TRANS_START_EVENT: {
127
        }
128
        break;
129
130
        case SPI_TRANS_DONE_EVENT: {
131
      xSemaphoreGive(mutex);
132
      //xTaskResumeFromISR(spi_task_handle);
133
        }
134
        break;
135
136
        case SPI_DEINIT_EVENT: {
137
        }
138
        break;
139
    }
140
}
141
142
void ICACHE_FLASH_ATTR app_main() {
143
  mutex = xSemaphoreCreateMutex();
144
  
145
  gpio_config_t io_conf;
146
  io_conf.intr_type = GPIO_INTR_DISABLE;
147
  io_conf.mode = GPIO_MODE_OUTPUT;
148
  io_conf.pin_bit_mask = LED_OUTPUT_PIN_SEL;
149
  io_conf.pull_down_en = 0;
150
  io_conf.pull_up_en = 0;
151
  gpio_config(&io_conf);
152
  
153
  spi_config_t spi_config;
154
  spi_config.interface.val = SPI_DEFAULT_INTERFACE;
155
  spi_config.intr_enable.val = SPI_MASTER_DEFAULT_INTR_ENABLE;
156
  spi_config.mode = SPI_MASTER_MODE;
157
  spi_config.clk_div = SPI_2MHz_DIV;
158
  spi_config.event_cb = &spi_callback;
159
  spi_init(HSPI_HOST, &spi_config);
160
}

Wenn ich anstatt des *Suspend()* und *Resume()* das Mutex verwende, 
funktioniert es (sobald ich xSemaphoreGive aus dem Callback entferne, 
bleibt der Controller stehen, daher gehe ich davon aus, dass die richtig 
funktionieren) im Sinne von "der Controller wartet, bis das SPI-Packet 
erfolgreich übertragen wurde". Bei Resume() und Suspend() bleibt der 
Controller nach dem 1. Suspend stehen.

Im Sinne des eigentlichen Zieles, dass 2 SPI-Pakete richtig übertragen 
werden, ist das Verhalten genauso wie im 1. Beitrag beschrieben.

minifloat schrieb:
> Wäre ja sonst sinnlos.

:(

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

minifloat schrieb:
> Ich hab da eine Frage für Dumme.
> Warum heißt es xTaskCreate
> Aber vTaskSuspend und vTaskResume?
>
> btw. Ich hab' keine Erfahrung mit FreeRTOS, alles nur höngengebliebenes
> Wissen aus dem Studium, was man eben über RTOS so vor 5-7 Jahren gelehrt
> hat.

Dann solltest du die aktuelle Doku untersuchen!
Dann stellst du schnell fest, dass die vFunktionen als void definiert 
sind, und die xFunktionen Daten zurückgeben.
Gibt och mehr solcher Präfixe

von minifloat (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Dann solltest du die aktuelle Doku untersuchen!

Ich bin nicht der TO. Werde mir daher nicht die Doku rein ziehen, erst 
wenn ich es benötige.
Warum leistest du angesichts deiner FreeRTOS-Allmacht keinen fachlich 
wertvollen Beitrag?

Arduino Fanboy D. schrieb:
> Gibt och mehr solcher Präfixe
Es gibt Leute die...
1 die Ungarische Notation verstehen und sinnvoll einsetzen
2 die Ungarische Notation für outdated halten, da Dank moderner IDEs 
obsolet
3 die Ungarische Notation mit Umgekehrter Polnischer Notation 
verwechseln und dich bei Diskussionen dann wie ein Auto anschauen
4 die Ungarische Notation nicht verstehen und sie falsch einsetzen
5 die Ungarische Notation durch etwas anderes ersetzen, was auch nicht 
sinnvoll ist

mfg mf

von Einer K. (Gast)


Lesenswert?

minifloat schrieb:
> Ich bin nicht der TO. Werde mir daher nicht die Doku rein ziehen, erst
> wenn ich es benötige.
> Warum leistest du angesichts deiner FreeRTOS-Allmacht keinen fachlich
> wertvollen Beitrag?

Warum fragst du dann?
Nur um hinterher rum maulen zu können?

Ich wünsche dir:
Frohe Weihnachten.
Und ein schönes neues Jahr, gerne auch ohne solche Ausfälle.

von Max M. (maxmicr)


Lesenswert?

Arduino Fanboy D. schrieb:
> Nur um hinterher rum maulen zu können?

Was ist mit dir falsch?



Ich sehe das aktuelle Problem nicht bei FreeRTOS sondern eher den 
System-Funktionen vom ESP8266 die schlecht / gar nicht dokumentiert 
sind.

von Fragender (Gast)


Lesenswert?

Max M. schrieb:
> Nun sind beide Nachrichten falsch

Doofe Frage am Rande: Welchen Logicanalyzer und Software nutzt du? Ist 
ähnlich zu Saleae, aber anderes Design. (Wollte mich noch nachträglich 
beschenken.)

von minifloat (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> [...] gerne auch ohne solche Ausfälle.

Danke, die Ausfälle gebe ich gern zurück.
Wieder kein fachlich sinnvoller Beitrag von dir.

BTT.
Max, dein Mutex würde den Thread erst pausieren, wenn der Callback 
bereits ausgeführt ist. So rennt der Thread weiter und nimmt sich die 
Semaphore.
Ein hässliches Konstrukt mit...

> Thread
> ...
> SemaTake
> SemaTake*
> SemaGive
> ...

> Callback
> ...
> SemaGive
> ...

...kam mir gerade in den Sinn. Der Thread würde beim zweiten SemTake (*) 
stoppen, der Callback gibt wieder frei wenn er soweit ist, Thread nimmt 
erfolgreich und gibt wieder ab.

Hast du schon andere weitere Erkenntnisse, Max?

von Max M. (maxmicr)


Angehängte Dateien:

Lesenswert?

Ich glaub ich habs *Heulen vor Freude*:
1
#define  LED_OUTPUT_PIN_SEL  (1 << GPIO_NUM_2)
2
3
#define READ_CMD      0x13
4
#define WRITE_CMD      0x12
5
6
bool isLEDon = false;
7
SemaphoreHandle_t mutex;
8
TaskHandle_t spi_task_handle;
9
10
typedef enum STATE {
11
  IDLE = 0,
12
  SENT_WRITE = 1,
13
  SENT_READ = 2
14
} STATE;
15
16
void ICACHE_FLASH_ATTR spi_master_write_slave_task(void *arg) {
17
  printf("SPI Write-Task started...\n");
18
  
19
  uint8_t trans_data[5];
20
  uint8_t recv_data[1];
21
22
  STATE state = IDLE;
23
  spi_trans_t trans;
24
  
25
  memset(&trans, 0, sizeof(trans));
26
  trans.bits.miso = 0;
27
  trans.bits.mosi = 0;
28
  trans.mosi = trans_data;
29
  trans.miso = recv_data;
30
  trans.addr = NULL;
31
  trans.cmd = NULL;
32
33
  const uint32_t addr = 5829;
34
  const uint8_t addrMSB =  (addr >> 16);
35
  const uint8_t addrMLSB = (addr >> 8);
36
  const uint8_t addrLSB = (addr & 0xFF);
37
38
  while(true) {
39
    switch(state) {
40
      case IDLE: {
41
        xSemaphoreTake(mutex, portMAX_DELAY);
42
        trans.bits.miso = 0;
43
        trans.bits.mosi = 5 * 8;
44
        trans_data[0] = WRITE_CMD;
45
        trans_data[1] = addrMSB;
46
        trans_data[2] = addrMLSB;
47
        trans_data[3] = addrLSB;
48
        trans_data[4] = 133;
49
        spi_trans(HSPI_HOST, &trans);
50
        //vTaskSuspend(spi_task_handle);
51
52
        state = SENT_WRITE;
53
      }
54
      break;
55
56
      case SENT_WRITE: {
57
        xSemaphoreTake(mutex, portMAX_DELAY);
58
        trans.bits.miso = 0;
59
        trans.bits.mosi = 4 * 8;
60
        trans_data[0] = READ_CMD;
61
        trans_data[1] = addrMSB;
62
        trans_data[2] = addrMLSB;
63
        trans_data[3] = addrLSB;
64
        spi_trans(HSPI_HOST, &trans);
65
        //vTaskSuspend(spi_task_handle);
66
67
        state = SENT_READ;
68
      }
69
      break;
70
71
      case SENT_READ: {
72
        xSemaphoreTake(mutex, portMAX_DELAY);
73
        trans.bits.mosi = 0;
74
        trans.bits.miso = 8;
75
        spi_trans(HSPI_HOST, &trans);
76
        state = IDLE;
77
      }
78
      break;
79
  }
80
81
  if(isLEDon) {
82
    gpio_set_level(GPIO_NUM_2, 1);
83
    isLEDon = false;
84
  } else {
85
    gpio_set_level(GPIO_NUM_2, 0);
86
    isLEDon = true;
87
  }
88
89
  vTaskDelay(100 / portTICK_PERIOD_MS);
90
  }
91
}
92
93
void spi_callback(int event, void *arg) {
94
    switch (event) {
95
        case SPI_INIT_EVENT: {
96
      xTaskCreate(spi_master_write_slave_task, "spi_master_write_slave", 2048, NULL, 3, &spi_task_handle);
97
        }
98
        break;
99
100
        case SPI_TRANS_START_EVENT: {
101
        }
102
        break;
103
104
        case SPI_TRANS_DONE_EVENT: {
105
      xSemaphoreGive(mutex);
106
      //xTaskResumeFromISR(spi_task_handle);
107
        }
108
        break;
109
110
        case SPI_DEINIT_EVENT: {
111
        }
112
        break;
113
    }
114
}
115
116
void ICACHE_FLASH_ATTR app_main() {
117
  mutex = xSemaphoreCreateMutex();
118
  
119
  gpio_config_t io_conf;
120
  io_conf.intr_type = GPIO_INTR_DISABLE;
121
  io_conf.mode = GPIO_MODE_OUTPUT;
122
  io_conf.pin_bit_mask = LED_OUTPUT_PIN_SEL;
123
  io_conf.pull_down_en = 0;
124
  io_conf.pull_up_en = 0;
125
  gpio_config(&io_conf);
126
  
127
  spi_config_t spi_config;
128
  spi_config.interface.val = SPI_DEFAULT_INTERFACE;
129
  spi_config.intr_enable.val = SPI_MASTER_DEFAULT_INTR_ENABLE;
130
  spi_config.mode = SPI_MASTER_MODE;
131
  spi_config.clk_div = SPI_2MHz_DIV;
132
  spi_config.event_cb = &spi_callback;
133
  spi_init(HSPI_HOST, &spi_config);
134
}

minifloat schrieb:
> So rennt der Thread weiter und nimmt sich die
> Semaphore.

Stimmt, mein Fehler.

Meinst du, das macht so mehr Sinn?

Fragender schrieb:
> Welchen Logicanalyzer und Software nutzt du? Ist
> ähnlich zu Saleae, aber anderes Design. (Wollte mich noch nachträglich
> beschenken.)

Ist ein Saleae-Klon aus China mit offizieller Software.

von minifloat (Gast)


Lesenswert?

Max M. schrieb:
> Meinst du, das macht so mehr Sinn?

Beim Zweiten Durchlauf des Tasks wird an der Semaphore gehalten. Das ist 
u.U. nicht sofort ersichtlich. Insgesamt aber logisch und sauberer als 
mein Vorschlag. Die Struktur "trans" und die Ressource 
"SPI-Schnittstelle" musste ja geschützt werden. Wenn die SPI noch busy 
ist, was du ja im Callback siehst, hat der Task nix dran rum zu fummeln.

mfg mf

von Max M. (maxmicr)


Lesenswert?

Ich hab inzwischen ein wenig weiter entwickelt. Man soll nun per 
GET-Aufruf auf die Seite "/set?addr=5423&value=123" einen SPI-Transmit 
mit eben den verwendeten Werten absetzen können (ebenso mit 
"get?addr=5423").

Da ich nun noch das ganze HTTP-Handler Zeug in der main.c hab, dachte 
ich mir der Übersicht halber, das SPI-Handling in eine extra Struktur 
auszulagern:
1
#define TRANS_ARRAY_LEN    5
2
#define  RECV_ARRAY_LEN    1
3
4
#define WRITE_REQUEST_SIZE  5
5
#define READ_REQUEST_SIZE  4
6
7
typedef enum STATE {
8
  IDLE = 0,
9
  READY = 1,
10
  WROTE_WRITE_CMD = 2,
11
  WROTE_READ_CMD = 3,
12
  SENT_READ_CLK = 4
13
} STATE;
14
15
typedef struct SPI_HANDLER {
16
  
17
  spi_trans_t spi_t;
18
  STATE state;
19
20
  uint8_t trans_data[TRANS_ARRAY_LEN];
21
  uint8_t recv_data[RECV_ARRAY_LEN];
22
  
23
  void (*finishedWriteRequestCallback)(void);
24
  void (*finishedReadRequestCallback)(uint8_t data);
25
  
26
  void (*writeToRAM)(struct SPI_HANDLER*, uint32_t addr, uint8_t data);
27
  void (*readFromRAM)(struct SPI_HANDLER*, uint32_t addr);
28
  void (*notifyTransmitFinished)(struct SPI_HANDLER*);
29
  
30
} SPI_HANDLER;
31
32
extern void initSPIHandler(SPI_HANDLER *self);

In der Initialisierung wird dann alles vorkonfiguriert:
1
void ICACHE_FLASH_ATTR initSPIHandler(SPI_HANDLER *self) {
2
  self->writeToRAM = &writeToRAMRequest;
3
  self->readFromRAM = &readFromRAMRequest;
4
  self->notifyTransmitFinished = &notifyTransmitFinished;
5
  
6
  self->state = IDLE;
7
  
8
  memset(&self->spi_t, 0, sizeof(self->spi_t));
9
  self->spi_t.bits.miso = 0;
10
  self->spi_t.bits.mosi = 0;
11
  self->spi_t.mosi = self->trans_data;
12
  self->spi_t.miso = self->recv_data;
13
  self->spi_t.addr = NULL;
14
  self->spi_t.cmd = NULL;
15
  
16
  printf("Finished SPIHandler initialization\n");
17
}

Wenn jemand nun z.B. "/get?addr=5423" eingibt, wird dieser Handler 
aufgerufen:
1
esp_err_t ICACHE_FLASH_ATTR get_handler(httpd_req_t *req) {
2
  printf("/get handler called...\n");
3
4
    char*  buf;
5
    size_t buf_len;
6
    buf_len = httpd_req_get_url_query_len(req) + 1;
7
    if (buf_len > 1) {
8
        buf = malloc(buf_len);
9
        if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
10
            char addr_str[7];
11
            if (httpd_query_key_value(buf, "addr", addr_str, sizeof(addr_str)) == ESP_OK) {
12
                printf("Found parameter addr=%s\n", addr_str);
13
        const uint32_t addr = (uint32_t)atoi(addr_str);
14
        printf("atoi() delivered: %d\n", addr);
15
        spiHandler.readFromRAM(&spiHandler, addr);
16
            }
17
        }
18
        free(buf);
19
    }
20
21
  char resp[100];
22
  sprintf(resp, "Reading...");
23
  httpd_resp_send(req, resp, strlen(resp));
24
25
    return ESP_OK;
26
}

Die Funktion die hinter dem Funktionspointer *spiHandler.readFromRAM* 
steckt, sieht so aus:
1
void ICACHE_FLASH_ATTR readFromRAMRequest(SPI_HANDLER *self, uint32_t addr) {
2
  printf("Read from RAM request...\n");
3
  if(self->state == READY) {
4
    self->spi_t.bits.miso = 0;
5
    self->spi_t.bits.mosi = READ_REQUEST_SIZE * 8;
6
7
    self->trans_data[0] = READ_CMD;
8
    fillArrayWithAddr(&self->trans_data[1], addr);
9
    self->state = WROTE_READ_CMD;
10
    spi_trans(HSPI_HOST, &self->spi_t); /* ESP stürzt ab */
11
  } else {
12
    printf("SPIHandler not ready\n");
13
  }
14
}

Nun stürzt der ESP8266 in der markierten Zeile leider ab :(
1
abort() was called at PC 0x40231393 on core 0
2
3
Guru Meditation Error: Core  0 panic'ed (StoreProhibited). Exception was unhandled.
4
5
Core 0 register dump:
6
PC      : 0x402311dd  PS      : 0x00000033  A0      : 0x402311d8  A1      : 0x3ffe8410  
7
8
A2      : 0x00000000  A3      : 0x00000000  A4      : 0x00000000  A5      : 0x00000001  
9
10
A6      : 0x00000001  A7      : 0x402549ac  A8      : 0x00000040  A9      : 0x00000001  
11
12
A10     : 0x0000002d  A11     : 0x00000040  A12     : 0x3ffecc90  A13     : 0xffffffff  
13
14
A14     : 0x00000004  A15     : 0x40106c54  SAR     : 0x0000001e  EXCCAUSE: 0x0000001d
1
29  StoreProhibited:  CPU tried to store memory from a region which is protected against writes

https://github.com/SuperHouse/esp-open-rtos/wiki/Crash-Dumps

Aha...

Ich hab leider keine Ahnung warum, sobald ich den Funktionsaufruf 
spi_trans rausnehme, stürzt er nicht mehr ab (aber es passiert 
natürlich auch nichts).

Ich weiß nicht, ob das wieder so ein "printf"-Ding ist wie beim 
SPI-Callback oder ob die interne WLAN-Routine zu wenig Rechenzeit 
bekommt und deswegen der Watchdog den ESP killt (kann ich mir ja fast 
nicht vorstellen, so viel passiert nun auch nicht). Den RTOS-Task hab 
ich auch rausgeschmissen, so wirklich viel Sinn hatte der nicht wenn das 
eh alles asynchron läuft mit dem SPI-Event. War das ein Fehler? Muss so 
ein Task existieren damit das RTOS vernünftig läuft?

Allgemein bin ich nicht so zufrieden, selbst wenn die aktuelle Lösung 
funktionieren würde, da die Antwort asynchron zum HTTP-GET Aufruf 
passiert.

Aktuell liefere ich nach dem Aufruf "Reading..." als HTTP-Response-Body 
zurück. Meine Idee wäre dann, sobald das Byte per SPI gelesen ist (krieg 
ich ja mit über das spi-event:
1
void ICACHE_FLASH_ATTR spi_callback(int event, void *arg) {
2
    switch (event) {
3
        case SPI_INIT_EVENT: {
4
      spiHandler.state = READY;
5
        }
6
        break;
7
8
        case SPI_TRANS_START_EVENT: {
9
        }
10
        break;
11
12
        case SPI_TRANS_DONE_EVENT: {
13
      spiHandler.notifyTransmitFinished(&spiHandler);
14
        }
15
        break;
16
17
        case SPI_DEINIT_EVENT: {
18
        }
19
        break;
20
    }
21
}
)

eine weitere HTTP-Antwort zu schicken. Aber da hab ich dann gar keinen 
Aufruf also kein *httpd_req_t*-Pointer den die Funktion 
httpd_resp_send zur Antwort benötigt - wie mache ich das denn nun?

Unglücklicherweise ist der *http_req_t*-Pointer "verbraucht" sobald man 
eine Antwort gesendet hat (laut einem Kommentar im Espressif-GitHub-Repo 
Example). Alternativ könnte ich gar keine Antwort schicken und mir den 
Pointer merken bis die Antwort da ist...?

Edit: Ich hab probeweise das
1
spi_trans(HSPI_HOST, &self->spi_t); /* ESP stürzt ab */

mit
1
while(true);

ersetzt, und der ESP stürzt erst nach ein paar Sekunden mit diesmal 
einer anderen Fehlermeldung ab:
1
Task watchdog got triggered.
2
3
4
5
Guru Meditation Error: Core  0 panic'ed (unknown). Exception was unhandled.
6
7
Core 0 register dump:
8
PC      : 0x4022bbc8  PS      : 0x00000030  A0      : 0x4022bbc8  A1      : 0x3fff4310  
9
10
A2      : 0x0000000a  A3      : 0x00000000  A4      : 0x00000001  A5      : 0x00000000  
11
12
A6      : 0x40230ecc  A7      : 0x00000000  A8      : 0x00000000  A9      : 0x00000001  
13
14
A10     : 0x00000000  A11     : 0x0000000a  A12     : 0x3ffead34  A13     : 0x40107bec  
15
16
A14     : 0x0000053a  A15     : 0x401076b0  SAR     : 0x0000001c  EXCCAUSE: 0x0000053a

Am Watchdog kanns also nicht liegen.

: Bearbeitet durch User
von minifloat (Gast)


Lesenswert?

Max M. schrieb:
> Den RTOS-Task hab ich auch rausgeschmissen, so wirklich viel Sinn hatte
> der nicht wenn das eh alles asynchron läuft mit dem SPI-Event.
Warum, das hat doch super funktioniert?

Kannst du einen Task erstellen, der zyklisch liest (nicht erst auf 
Anfrage) und einen, der sich um HTTP kümmert?
Datenübergabe dann über eine Struktur, die per Mutex geschützt wird.

Oder geht es dir darum, per HTTP-get und -post gezielt SPI-Transfers 
auszulösen?

Ich hab mit dem HTTP-gedöns auch schon ein wenig herumgespielt, 
allerdings ohne RTOS. Dabei ist es von Vorteil gewesen, Daten für HTTP 
(die "Internetseite") dauerhaft bereit zu halten. Datenaustausch erfolgt 
über hin-und-her gehende xml-Objekte. Auf der "Internetseite" läuft 
Javascript, um zyklisch oder auf Trigger hin Daten auszutauschen.

mfg mf

von minifloat (Gast)


Lesenswert?

PS. Leider bin ich bis morgen Abend außer Haus,  sonst hätte ich dir 
entsprechende Codeschnipsel  Posten können...

von Max M. (maxmicr)


Lesenswert?

minifloat schrieb:
> Warum, das hat doch super funktioniert?

Ich hatte alles in der main.c und das sah furchtbar aus. Deswegen hab 
ich versucht, den SPI-Teil in eben jene extra Struktur auszulagern.

minifloat schrieb:
> Oder geht es dir darum, per HTTP-get und -post gezielt SPI-Transfers
> auszulösen?

Ja genau, man soll mit dem Aufrufen von "/get?addr=xxxxxx" einen 
SPI-Transmit (READ_CMD) auslösen können. Diese Nachricht geht an einen 
ATmega, der dann entsprechend eine Antwort zurückliefert (natürlich 
leider asynchron, d.h. ich weiß (noch) nicht wie ich das mit der Antwort 
dann mache).
Genau das gleiche soll mit "/set?addr=xxxxxx&value=xxx" passieren, nur 
eben WRITE_CMD (und es gibt keine Antwort vom ATmega).

minifloat schrieb:
> Kannst du einen Task erstellen, der zyklisch liest (nicht erst auf
> Anfrage) und einen, der sich um HTTP kümmert?

Sowas fände ich sehr schön, ich hab aus 2017 ein Codebeispiel gefunden 
(das hat nicht funktioniert), die Beispiele vom Espressif-GitHub-Repo 
nutzen HTTP-Request-Handler.

Man legt so eine Struktur an:
1
httpd_uri_t set = {
2
    .uri       = "/set",
3
    .method    = HTTP_GET,
4
    .handler   = set_handler,
5
    .user_ctx  = NULL
6
};

registriert sie beim Webserver
1
httpd_handle_t ICACHE_FLASH_ATTR start_webserver(void)
2
{
3
    httpd_handle_t server = NULL;
4
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
5
6
    printf("Starting server on port: '%d'\n", config.server_port);
7
    if (httpd_start(&server, &config) == ESP_OK) {
8
        printf("Registering URI handlers\n");
9
        httpd_register_uri_handler(server, &get);
10
    httpd_register_uri_handler(server, &set);
11
12
        return server;
13
    }
14
15
    printf("Error starting server!\n");
16
    return NULL;
17
}

und hat einen Handler wie im letzten Beitrag schon gezeigt. D.h. ich 
wüsste nicht, wie ich da einen Task drum rum basteln soll.

Sobald ich die mosi/miso-Arrays, das Anlagen der *spi_trans_t*-Struktur 
wieder nach *main.c*-Verschiebe und spi_trans auch nur dort aufrufe, 
stürzt der ESP nicht mehr ab. Ich weiß nicht, ob sowas möglich wäre, 
aber kann es sein, dass es der ESP einfach nicht mag wenn man in 
externen C-Files die Strukturen für Systemfunktionen anlegt

minifloat schrieb:
> Dabei ist es von Vorteil gewesen, Daten für HTTP
> (die "Internetseite") dauerhaft bereit zu halten.

Ist bestimmt eine gute Idee, aber ich kann nicht vorhersagen, was der 
Benutzer für eine Adresse und Wert eingibt.

minifloat schrieb:
> Leider bin ich bis morgen Abend außer Haus,  sonst hätte ich dir
> entsprechende Codeschnipsel  Posten können...

Mach dir kein Kopf, reicht schon wenn sich einer damit rumärgern müss :/

minifloat schrieb:
> Auf der "Internetseite" läuft
> Javascript, um zyklisch oder auf Trigger hin Daten auszutauschen.

So hatte ich mir das auch gedacht aber ich hab keine Ahnung wie die 
Kommunikation zwischen JavaScript und ESP abläuft.

: Bearbeitet durch User
von minifloat (Gast)


Lesenswert?

Max M. schrieb:
> wie ich da einen Task drum rum basteln soll.

Ein Task, der zyklisch die Daten vom SPI in einen Puffer schaufelt. Die 
Get-Handler in der httpd_uri_t Struktur sind auch wieder so lustige 
Callbacks.

Max M. schrieb:
> minifloat schrieb:
>> Auf der "Internetseite" läuft
>> Javascript, um zyklisch oder auf Trigger hin Daten auszutauschen.
>
> So hatte ich mir das auch gedacht aber ich hab keine Ahnung wie die
> Kommunikation zwischen JavaScript und ESP abläuft.

Da kann ich dir morgen Abend paar Schnipsel Javascript dazu posten. Wie 
gesagt ein XML-Objekt je Kommunikationsrichtung.
mfg mf

von minifloat (Gast)


Lesenswert?

PS. Ich denke, man kann den HTTP-Get-Callback sicher mit einem Mutex 
kurz pausieren. Da scheint die SPI-Mimik bereits nicht mehr kurz genug.

Was hältst du von folgendem:
1) HTTP-get eine Seite mit Bedienoberfläche und Javascript. Dieses 
Javascript macht dann:
2) HTTP-post ein XML mit den abzusetzenden SPI-Daten(Mosi).
3) HTTP-get ein XML mit
   a) BinNochNichtFertig
   b) BinFertig mit empfangenen SPI-Daten(Miso)

Es empfiehlt sich, die Mosi-Daten und die BinNochNichtFertig- bzw. 
BinFertig-XMLs mit einer ID zu versehen, sonst ist die Synchronisation 
der Benutzeroberfläche Gulasch.
Wenn nochmal Mosi-XML eintrudeln, ohne dass Miso-XML abgeholt wurde, 
wird Miso einfach gelöscht und ein neuer Transferzyklus kann beginnen.

mfg mf

von minifloat (Gast)


Lesenswert?

PS. Statt XML-Objekten geht auch JSON.

von Max M. (maxmicr)


Angehängte Dateien:

Lesenswert?

Kann es sein, dass sich WiFi und SPI nicht vertragen?

So sehe ich den Access-Point des ESP:
1
WEBSERVER webserver;
2
3
void ICACHE_FLASH_ATTR spi_callback(int event, void *arg) {
4
    switch (event) {
5
        case SPI_INIT_EVENT: {
6
7
        }
8
        break;
9
10
        case SPI_TRANS_START_EVENT: {
11
        }
12
        break;
13
14
        case SPI_TRANS_DONE_EVENT: {
15
16
        }
17
        break;
18
19
        case SPI_DEINIT_EVENT: {
20
        }
21
        break;
22
    }
23
}
24
25
void ICACHE_FLASH_ATTR app_main() {
26
  gpio_config_t io_conf = {
27
    .intr_type = GPIO_INTR_DISABLE,
28
    .mode = GPIO_MODE_OUTPUT,
29
    .pin_bit_mask = LED_OUTPUT_PIN_SEL,
30
    .pull_down_en = 0,
31
    .pull_up_en = 0
32
  };
33
  gpio_config(&io_conf);
34
  gpio_set_level(GPIO_NUM_2, LED_OFF);
35
  
36
  spi_config_t spi_config = {
37
    .interface.val = SPI_DEFAULT_INTERFACE,
38
    .intr_enable.val = SPI_MASTER_DEFAULT_INTR_ENABLE,
39
    .mode = SPI_MASTER_MODE,
40
    .clk_div = SPI_2MHz_DIV,
41
    .event_cb = &spi_callback
42
  };
43
  spi_init(HSPI_HOST, &spi_config);
44
  
45
  initWebserver(&webserver, "Projekt", "");
46
}

und so nicht:
1
SPI_HANDLER spiHandler;
2
WEBSERVER webserver;
3
4
void ICACHE_FLASH_ATTR spi_callback(int event, void *arg) {
5
    switch (event) {
6
        case SPI_INIT_EVENT: {
7
      spiHandler.state = READY;
8
        }
9
        break;
10
11
        case SPI_TRANS_START_EVENT: {
12
        }
13
        break;
14
15
        case SPI_TRANS_DONE_EVENT: {
16
      
17
        }
18
        break;
19
20
        case SPI_DEINIT_EVENT: {
21
        }
22
        break;
23
    }
24
}
25
26
void ICACHE_FLASH_ATTR app_main() {
27
  gpio_config_t io_conf = {
28
    .intr_type = GPIO_INTR_DISABLE,
29
    .mode = GPIO_MODE_OUTPUT,
30
    .pin_bit_mask = LED_OUTPUT_PIN_SEL,
31
    .pull_down_en = 0,
32
    .pull_up_en = 0
33
  };
34
  gpio_config(&io_conf);
35
  gpio_set_level(GPIO_NUM_2, LED_OFF);
36
  
37
  spi_config_t spi_config = {
38
    .interface.val = SPI_DEFAULT_INTERFACE,
39
    .intr_enable.val = SPI_MASTER_DEFAULT_INTR_ENABLE,
40
    .mode = SPI_MASTER_MODE,
41
    .clk_div = SPI_2MHz_DIV,
42
    .event_cb = &spi_callback
43
  };
44
  spi_init(HSPI_HOST, &spi_config);
45
  
46
  initSPIHandler(&spiHandler);
47
  initWebserver(&webserver, "Projekt", "");
48
}

Das kann doch nicht sein? ???

: Bearbeitet durch User
von minifloat (Gast)


Angehängte Dateien:

Lesenswert?

minifloat schrieb:
> Da kann ich dir morgen Abend paar Schnipsel Javascript dazu posten.

Habe herade gesehen, dass ich das Ding "mit Arduino" erstellt habe.
Leider nur eine unfertige Version. Das Konzept sollte aber klar sein.
Dann muss ich mich bei dir entschuldigen, das hier...

minifloat schrieb:
> Wie gesagt ein XML-Objekt je Kommunikationsrichtung.

...stimmt bei meiner Umsetzung nicht.

Ich lade die HTML-Seite, diese lädt sich ein XML. Im XML befinden sich 
die Daten, die von den Bedienelementen dargestellt werden 
sollen(ESP8266->Webseite). Kommandos (Webseite->ESP8266) werden über den 
requeststring des XMLs ausgetauscht.

mfg mf

von Max M. (maxmicr)


Lesenswert?

Vielen Dank für deine Hilfe. Das sieht gut aus, dann weiß ich ungefähr 
wie ich das machen muss.

Max M. schrieb:
> Das kann doch nicht sein? ???

Hab das Problem inzwischen tatsächlich mit zwei Queues gelöst bekommen, 
die sich mit dem FreeRTOS sehr komfortabel erstellen lassen.
Ich hatte da ein paar Pointer / Struktur-Fehler die beim ESP für sehr 
seltsames Verhalten gesorgt haben (aber keine Absturzmeldung) weswegen 
ich darauf erstmal nicht aufmerksam geworden bin.

Falls es jemanden nützt, so sieht nun der Event-Handler des Webservers 
aus:
1
esp_err_t ICACHE_FLASH_ATTR get_handler(httpd_req_t *req) {
2
  printf("/get handler called...\n");
3
4
    char*  buf;
5
    size_t buf_len;
6
  char resp_msg[100];
7
    buf_len = httpd_req_get_url_query_len(req) + 1;
8
    if (buf_len > 1) {
9
        buf = malloc(buf_len);
10
        if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
11
            char addr_str[7];
12
            if (httpd_query_key_value(buf, "addr", addr_str, sizeof(addr_str)) == ESP_OK) {
13
                printf("Found parameter addr=%s\n", addr_str);
14
    const uint32_t addr = (uint32_t)atoi(addr_str);
15
    printf("atoi() delivered: %d\n", addr);
16
    
17
    SPI_RESPONSE spi_resp;
18
    SPI_REQUEST spi_req = {
19
      .type = READ_CMD,
20
      .addr = addr,
21
      .data = 0
22
    };
23
24
    uint8_t ret = xQueueSend(webserver.toSPIQueue, &spi_req, 0);
25
    if(ret == pdTRUE) {
26
      ret = xQueueReceive(webserver.toWebserverQueue, &spi_resp, 5000 / portTICK_RATE_MS);
27
      
28
      if(ret == pdTRUE) {
29
        sprintf(resp_msg, "Value=%d", spi_resp.data);
30
      } else {
31
        sprintf(resp_msg, "Didn't receive answer within 5s");
32
      }
33
    } else {
34
      sprintf(resp_msg, "Request-Queue of SPIHandler is full");
35
    }
36
    
37
    httpd_resp_send(req, resp_msg, strlen(resp_msg));
38
            }
39
        }
40
        free(buf);
41
    }
42
43
    return ESP_OK;
44
}

Und so der SPIHandler-Task (der Webserver selbst hat keinen Task):
1
void ICACHE_FLASH_ATTR spi_handler_task(void *arg) {
2
  
3
  SPI_REQUEST req;
4
  SPI_RESPONSE resp;
5
6
  while(true) {
7
    if(spiHandler.state == READY) {
8
      const uint8_t queue_resp = xQueueReceive(spiHandler.toSPIQueue, &req, 0);
9
      if(queue_resp && xSemaphoreTake(spiHandler.mutex, portMAX_DELAY )) {
10
        printf("SPIHandler: Got SPI_REQUEST...");
11
        if(req.type == READ_CMD) {
12
          printf("of type READ with %d\n", req.addr);
13
          uint8_t trans_data[READ_REQUEST_SIZE];
14
          fillReadRequest(trans_data, req.addr);
15
16
          spiHandler.spi_t.bits.mosi = READ_REQUEST_SIZE * 8;
17
          spiHandler.spi_t.bits.miso = 0;
18
          spiHandler.spi_t.mosi = trans_data;
19
          printf("Set up SPI buffer, transmitting...");
20
          spi_trans(HSPI_HOST, &spiHandler.spi_t);
21
          printf("done\n");
22
          spiHandler.state = WROTE_READ_CMD;
23
        } else if(req.type == WRITE_CMD) {
24
          printf("of type WRITE with %d|%d\n", req.addr, req.data);
25
          uint8_t trans_data[WRITE_REQUEST_SIZE];
26
          fillWriteRequest(trans_data, req.addr, req.data);
27
          spiHandler.spi_t.bits.mosi = WRITE_REQUEST_SIZE * 8;
28
          spiHandler.spi_t.bits.miso = 0;
29
          spiHandler.spi_t.mosi = trans_data;
30
          printf("Set up SPI buffer, transmitting...");
31
          spi_trans(HSPI_HOST, &spiHandler.spi_t);
32
          printf("done\n");
33
          spiHandler.state = WROTE_WRITE_CMD;
34
        }
35
      }
36
    } else {
37
      if(xSemaphoreTake(spiHandler.mutex, portMAX_DELAY)) {
38
        switch(spiHandler.state) {
39
          case WROTE_READ_CMD: {
40
            printf("Substate 'WROTE_READ_CMD' for READ-REQUEST, setting up SPI buffer...");
41
            spiHandler.spi_t.bits.mosi = 0;
42
            spiHandler.spi_t.bits.miso = 8;
43
            spiHandler.spi_t.miso = recv_data;
44
            printf("done. Transmitting...");
45
            spi_trans(HSPI_HOST, &spiHandler.spi_t);
46
            printf("done\n");
47
            spiHandler.state = SENT_READ_CLK;
48
          } break;
49
          case SENT_READ_CLK: {
50
            printf("Substate 'SENT_READ_CLK' for READ-Request, received response\n");
51
            resp.addr = req.addr;
52
            resp.data = recv_data[0];
53
            spiHandler.state = READY;
54
            printf("Push result into queue...");
55
            xQueueSend(spiHandler.toWebserverQueue, &resp, 0);
56
            printf("done\n");
57
            xSemaphoreGive(spiHandler.mutex);
58
          } break;
59
          case WROTE_WRITE_CMD: {
60
            xQueueSend(spiHandler.toWebserverQueue, &resp, 0);
61
            spiHandler.state = READY;
62
            xSemaphoreGive(spiHandler.mutex);
63
          } break;
64
          default: break;
65
        }
66
      }
67
    }
68
    
69
    vTaskDelay(10 / portTICK_RATE_MS);
70
  }
71
}

Ursprünglich wollte ich zwei Arrays, die ich in der Struktur 
SPI_HANDLER angelegt habe, für MISO/MOSI verwenden. Aber da wurden 
dann wieder mehrere Nullen übertragen (wie im ursprünglichen Beitrag).

Mit lokalen Arrays so wie oben dargestellt, die eigentlich außerhalb des 
Scopes liegen, funktioniert es dann seltsamerweise.
So ganz verstanden hab ich es noch nicht.

von minifloat (Gast)


Lesenswert?

Max M. schrieb:
> Vielen Dank für deine Hilfe.

Bitte gerne. Note to self: Lichtsteuerung zuende coden und die seit 3 
Jahren laufene noch üblere Bastelversion damit ersetzen...

Schön, dass es jetzt ein bisschen mehr funzt :)

mfg mf

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.