esp32-gameboy-printer/main/gb_printer.c
Tido Klaassen 9c6714b2a6 Improve Error Detection and Code Cleanup
- Improve detection and handling of broken packet, make sure errors
  are signalled back to the Game Boy.
- Some code cleanup, add comments.
2018-08-28 19:25:07 +02:00

1081 lines
30 KiB
C

/*
* This file is part of the GameBoy-Printer project.
* Copyright (C) 2018 Tido Klaassen <tido_gbprinter@4gh.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "lwip/igmp.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "soc/rtc_cntl_reg.h"
#include "rom/cache.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
#include "esp_spiffs.h"
#include "soc/gpio_reg.h"
#include "driver/gpio.h"
#include "esp_intr_alloc.h"
#include <libesphttpd/esp.h>
#include <libesphttpd/httpd.h>
#include <libesphttpd/httpdespfs.h>
#include <libesphttpd/cgiflash.h>
#include <libesphttpd/auth.h>
#include <libesphttpd/espfs.h>
#include <libesphttpd/captdns.h>
#include <libesphttpd/webpages-espfs.h>
#include <libesphttpd/httpd-freertos.h>
#include <libesphttpd/route.h>
#include <libesphttpd/esp32_httpd_vfs.h>
#include "lodepng.h"
#define TAG "GB-Printer"
#define GPIO_MISO 4
#define GPIO_MOSI 13
#define GPIO_SCLK 14
#define WIFI_SSID "GB-Printer"
#define MAX_STA_CONN 3
#define LISTEN_PORT 80u
#define MAX_CONNECTIONS 8u
#define NVS_NAMESPC "GBPR"
#define IMGDIR "/img"
#define IMGTMPL "img%05d.png"
#define IMG_WIDTH 160 // Images are always 160 pixels wide
#define IMG_HEIGHT 144 // Maximum image height, may be less
#define IMG_SIZE 5760
#define MAX_DATA_SIZE 640
#define PR_PARAM_SIZE 4
#define PKT_RING_SIZE 4
#define HTML_HEADER "<html>" \
"<head>" \
"<meta charset=\"utf-8\">" \
"<title>ESP32 GameBoy Printer</title>" \
"<link rel=\"stylesheet\" href=\"style.css\" " \
"media=\"screen\" title=\"no title\" " \
"charset=\"utf-8\">" \
"</head>"
/* Useful Linux-like defines */
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
#define mb() asm volatile ("" : : : "memory")
#define STATUS_CHKSUM (1u << 0)
#define STATUS_BUSY (1u << 1)
#define STATUS_FULL (1u << 2)
#define STATUS_UNPROC (1u << 3)
#define STATUS_ERR0 (1u << 4)
#define STATUS_ERR1 (1u << 5)
#define STATUS_ERR2 (1u << 6)
#define STATUS_BATLOW (1u << 7)
enum pr_state {
state_sync0 = 0,
state_sync1,
state_cmd,
state_compr,
state_len_low,
state_len_high,
state_data,
state_chk_low,
state_chk_high,
state_ack,
state_status,
};
enum pr_cmd {
cmd_init = 0x01,
cmd_print = 0x02,
cmd_data = 0x04,
cmd_break = 0x08,
cmd_inquiry = 0x0f,
};
enum pr_owner {
own_isr = 0,
own_task,
};
struct pr_packet {
volatile enum pr_owner owner;
enum pr_state state;
uint8_t cmd;
uint8_t compr;
uint8_t data[MAX_DATA_SIZE];
size_t data_len;
size_t cur_len;
uint16_t cur_sum;
uint16_t chk_sum;
};
struct pr_data {
uint8_t data[IMG_SIZE];
size_t data_len;
uint8_t params[PR_PARAM_SIZE];
uint8_t status;
unsigned int busy_cnt;
unsigned int printed;
size_t width;
size_t hight;
};
static struct spi_ctrl {
struct pr_packet work_packet;
struct pr_packet packets[PKT_RING_SIZE];
size_t clk_cnt;
uint64_t clk_last;
uint8_t rcv_data;
uint8_t snd_data;
volatile size_t wr_idx;
volatile size_t rd_idx;
volatile uint8_t status_isr;
volatile uint8_t status_task;
xQueueHandle packet_done;
} ctrl;
static uint8_t render_buff[IMG_SIZE];
static QueueHandle_t img_queue;
static EventGroupHandle_t wifi_event_group;
static char conn_mem[sizeof(RtosConnType) * MAX_CONNECTIONS];
static HttpdFreertosInstance httpd_instance;
CgiStatus cgi_del_all(HttpdConnData *conn);
CgiStatus cgi_reset_seq(HttpdConnData *conn);
CgiStatus tpl_index(HttpdConnData *conn, char *token, void **arg);
HttpdBuiltInUrl builtin_urls[]={
ROUTE_REDIRECT("/", "/index.tpl"),
ROUTE_REDIRECT("/index.html", "/index.tpl"),
ROUTE_TPL("/index.tpl", tpl_index),
ROUTE_CGI("/img/*", cgiEspVfsHook),
ROUTE_CGI("/remove_all", cgi_del_all),
ROUTE_CGI("/reset_seq", cgi_reset_seq),
ROUTE_FILESYSTEM(),
ROUTE_END()
};
CgiStatus tpl_index(HttpdConnData *conn, char *token, void **arg)
{
struct dirent *ent;
DIR *dir;
char buff[128];
CgiStatus result;
dir = (DIR *) *arg;
result = HTTPD_CGI_DONE;
buff[0] = '\0';
if(conn->isConnectionClosed){
ESP_LOGD(TAG, "[%s] Conn closed", __func__);
if(dir != NULL){
closedir(dir);
}
goto err_out;
}
if(token == NULL){
goto err_out;
}
if(strcmp(token, "GBPics") != 0){
goto err_out;
}
if(dir == NULL){
dir = opendir(IMGDIR);
if(dir == NULL){
ESP_LOGE(TAG, "[%s] opendir() failed", __func__);
goto err_out;
}
*arg = dir;
}
ent = readdir(dir);
if(ent == NULL){
goto err_out;
}
snprintf(buff, sizeof(buff),
"<br>"
"<a href=\"%s/%s\">"
"<img src=\"%s/%s\">"
"</a>",
IMGDIR, ent->d_name,
IMGDIR, ent->d_name);
result = HTTPD_CGI_MORE;
ESP_LOGD(TAG, "[%s] Sending: %s", __func__, buff);
httpdSend(conn, buff, -1);
err_out:
if(result == HTTPD_CGI_DONE && dir != NULL){
closedir(dir);
*arg = NULL;
}
return result;
}
CgiStatus cgi_del_all(HttpdConnData *conn)
{
struct dirent *ent;
DIR *dir;
char buff[128];
int result;
buff[0] = '\0';
dir = NULL;
result = 0;
if(conn->isConnectionClosed){
goto err_out;
}
dir = opendir(IMGDIR);
if(dir == NULL){
ESP_LOGE(TAG, "[%s] opendir() failed", __func__);
result = -1;
goto send_reply;
}
while((ent = readdir(dir)) != NULL){
snprintf(buff, sizeof(buff), "%s/%s", IMGDIR, ent->d_name);
result = unlink(buff);
if(result != 0){
ESP_LOGE(TAG, "[%s] Unlink failed for %s", __func__, buff);
goto send_reply;
}
}
send_reply:
if(result == 0){
httpdRedirect(conn, "/index.tpl");
} else {
httpdStartResponse(conn, 500);
httpdHeader(conn, "Content-Type", "text/html");
httpdEndHeaders(conn);
httpdSend(conn, HTML_HEADER, -1);
httpdSend(conn, "<body>"
"<h1>ESP32 Gameboy Printer</h1>"
"<p>"
"Error while deleting file ", -1);
httpdSend(conn, buff, -1);
httpdSend(conn, ".<br>"
"<a href=\"/index.tpl\">Go back</a>"
"<br>"
"<a href=\"/remove_all\">Retry</a>"
"</p>"
"</body>"
"</html>", -1);
}
err_out:
if(dir != NULL){
closedir(dir);
}
return HTTPD_CGI_DONE;
}
CgiStatus cgi_reset_seq(HttpdConnData *conn)
{
nvs_handle handle;
int result;
result = 0;
if(conn->isConnectionClosed){
goto err_out;
}
result = nvs_open(NVS_NAMESPC, NVS_READWRITE, &handle);
if(result != ESP_OK){
goto send_reply;
}
result = nvs_set_u32(handle, "next_idx", 0);
nvs_commit(handle);
nvs_close(handle);
send_reply:
if(result == ESP_OK){
httpdRedirect(conn, "/index.tpl");
} else {
httpdStartResponse(conn, 500);
httpdHeader(conn, "Content-Type", "text/html");
httpdEndHeaders(conn);
httpdSend(conn, HTML_HEADER, -1);
httpdSend(conn, "<body>"
"<h1>ESP32 Gameboy Printer</h1>"
"<p>"
"Error resetting image sequence."
"<br>"
"<a href=\"/index.tpl\">Go back</a>"
"<br>"
"<a href=\"/reset_seq\">Retry</a>"
"</p>"
"</body>"
"</html>", -1);
}
err_out:
return HTTPD_CGI_DONE;
}
esp_err_t http_srv_init(void)
{
HttpdInitStatus status;
esp_err_t result;
result = ESP_OK;
if(espFsInit((void*)(webpages_espfs_start)) != ESPFS_INIT_RESULT_OK){
result = ESP_FAIL;
goto err_out;
}
status = httpdFreertosInit(&httpd_instance,
builtin_urls,
LISTEN_PORT,
conn_mem,
MAX_CONNECTIONS,
HTTPD_FLAG_NONE);
if(status != InitializationSuccess){
result = ESP_FAIL;
goto err_out;
}
httpdFreertosStart(&httpd_instance);
err_out:
return result;
}
void draw_tile(uint8_t *data, uint8_t *image)
{
int x, y;
uint16_t *tmp;
for(y = 0; y < 8; ++y){
tmp = (uint16_t *) &image[40 * y];
*tmp = 0;
for(x = 7; x >= 0; --x){
*tmp |= ((data[1] >> x) & 0x1) << (2 * x + 1);
*tmp |= ((data[0] >> x) & 0x1) << (2 * x);
}
*tmp = ~htons(*tmp);
data += 2;
}
}
esp_err_t draw_bitmap(struct pr_data *data)
{
size_t w, h, x, y;
uint8_t *tile_data, *tile_dest;
unsigned int tile;
esp_err_t result;
result = ESP_OK;
w = IMG_WIDTH;
h = (data->data_len * 4) / IMG_WIDTH;
if(h > IMG_HEIGHT){
ESP_LOGE(TAG, "[%s] Invalid image hight: %d", __func__, h);
result = ESP_ERR_INVALID_SIZE;
goto err_out;
}
memset(render_buff, 0x0, sizeof(render_buff));
for(tile = 0; tile < (w * h / 64); ++tile){
x = tile % 20;
y = 8 * (tile / 20);
tile_data = &(data->data[tile * 16]);
tile_dest = &(render_buff[(x * 2) + (y * 40)]);
draw_tile(tile_data, tile_dest);
}
memmove(data->data, render_buff, data->data_len);
data->width = w;
data->hight = h;
err_out:
return result;
}
void save_data(struct pr_data *data)
{
struct stat statbuf;
uint32_t idx;
FILE *file;
char path[128];
nvs_handle handle;
size_t written, png_size;
uint8_t *png;
LodePNGState state;
esp_err_t result;
png = NULL;
file = NULL;
result = nvs_open(NVS_NAMESPC, NVS_READWRITE, &handle);
ESP_ERROR_CHECK(result);
result = draw_bitmap(data);
if(result != ESP_OK){
goto err_out;
}
lodepng_state_init(&state);
state.info_raw.colortype = LCT_GREY;
state.info_raw.bitdepth = 2;
state.info_png.color.colortype = LCT_GREY;
state.info_png.color.bitdepth = 2;
state.encoder.zlibsettings.btype = 0; // disable compression due to lack of memory
lodepng_encode(&png, &png_size, data->data, data->width, data->hight, &state);
result = state.error;
lodepng_state_cleanup(&state);
if(result != 0){
ESP_LOGE(TAG, "[%s] lodepng failed: %s", __func__, lodepng_error_text(result));
goto err_out;
}
result = nvs_get_u32(handle, "next_idx", &idx);
if(result != ESP_OK){
ESP_LOGI(TAG, "[%s] No image sequence number found", __func__);
idx = 0;
}
/* Find first free image name. Start at index read from NVS and increment
* until we find a free slot. */
errno = 0;
do{
snprintf(path, sizeof(path) - 1, IMGDIR "/" IMGTMPL, idx);
result = stat(path, &statbuf);
if(result == 0){
ESP_LOGD(TAG, "[%s] Found %s", __func__, path);
++idx;
}
}while(result == 0);
if(errno != ENOENT){
ESP_LOGE(TAG, "Saving data failed: %s", strerror(errno));
goto err_out;
}
file = fopen(path, "w");
if(file == NULL){
ESP_LOGE(TAG, "Failed to open file %s: %s", path, strerror(errno));
goto err_out;
}
written = fwrite(png, 1, png_size, file);
if(written != png_size){
ESP_LOGE(TAG, "Error writing data: %s", strerror(errno));
result = EIO;
goto err_out;
}
fclose(file);
file = NULL;
if(nvs_set_u32(handle, "next_idx", idx + 1) != ESP_OK){
/* Not super critical. Worst case we get a duplicate image name if
* picture storage gets erased before next image is saved */
ESP_LOGW(TAG, "[%s] Error storing image sequence number", __func__);
}
nvs_commit(handle);
err_out:
if(file != NULL){
fclose(file);
if(result != ESP_OK){
unlink(path);
}
}
if(png != NULL){
free(png);
}
nvs_close(handle);
return;
}
esp_err_t init_spiffs(void)
{
esp_err_t result;
esp_vfs_spiffs_conf_t cfg;
ESP_LOGI(TAG, "Initializing SPIFFS");
cfg.base_path = "/img";
cfg.partition_label = NULL;
cfg.max_files = MAX_CONNECTIONS;
cfg.format_if_mount_failed = true;
result = esp_vfs_spiffs_register(&cfg);
switch(result){
case ESP_OK:
break;
case ESP_FAIL:
ESP_LOGE(TAG, "Failed to mount or format filesystem");
break;
case ESP_ERR_NOT_FOUND:
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
break;
default:
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(result));
break;
}
return result;
}
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id) {
case SYSTEM_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "station:"MACSTR" join, AID=%d",
MAC2STR(event->event_info.sta_connected.mac),
event->event_info.sta_connected.aid);
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, "station:"MACSTR"leave, AID=%d",
MAC2STR(event->event_info.sta_disconnected.mac),
event->event_info.sta_disconnected.aid);
break;
default:
break;
}
return ESP_OK;
}
void wifi_init_softap()
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
wifi_config_t wifi_config = {
.ap = {
.ssid = WIFI_SSID,
.ssid_len = strlen(WIFI_SSID),
.max_connection = MAX_STA_CONN,
.authmode = WIFI_AUTH_OPEN,
},
};
wifi_event_group = xEventGroupCreate();
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
}
static void IRAM_ATTR sclk_isr_handler(void* arg)
{
struct spi_ctrl *ctrl;
struct pr_packet *packet;
uint64_t curr_time;
BaseType_t task_woken;
ctrl = (struct spi_ctrl *) arg;
packet = &(ctrl->work_packet);
if(gpio_get_level(GPIO_SCLK) == 0){
/* Falling edge. Check timeouts and set up MISO. */
curr_time = esp_timer_get_time();
/* Check for sync loss during byte transfer. Clock should run */
/* at ~8kHz (125us), so anything above 200us is definitely out */
/* of spec. */
if(unlikely(ctrl->clk_cnt != 0 && (curr_time - ctrl->clk_last) > 200)){
ctrl->clk_cnt = 0;
ctrl->rcv_data = 0;
ctrl->snd_data = 0;
ctrl->status_isr |= STATUS_ERR0;
packet->state = state_sync0;
}
/* According to the Game Boy programming manual the maximum allowed */
/* pause between sending bytes in a packet is 5ms. */
if(unlikely(packet->state != state_sync0
&& (curr_time - ctrl->clk_last) > 5100))
{
ctrl->status_isr |= STATUS_ERR0;
packet->state = state_sync0;
}
ctrl->clk_last = curr_time;
gpio_set_level(GPIO_MISO, (ctrl->snd_data & 0x80u) ? 1 : 0);
ctrl->snd_data <<= 1;
} else {
// Rising edge. Sample MOSI and increase clock count.
if(unlikely(ctrl->clk_cnt == 0)){
ctrl->rcv_data = 0;
}
ctrl->rcv_data <<= 1;
if(gpio_get_level(GPIO_MOSI)){
ctrl->rcv_data |= 1;
}
++ctrl->clk_cnt;
}
if(likely(ctrl->clk_cnt <= 7)){
goto done;
}
ctrl->clk_cnt = 0;
if(packet->state >= state_cmd && packet->state < state_chk_low){
packet->cur_sum += ctrl->rcv_data;
}
/* Process the transferred byte. The state indicates how the byte that */
/* has just completed is interpreted. If data needs to be sent out in */
/* state x, it must be copied to ctrl->snd_data during transition to */
/* that state. */
switch(packet->state){
case state_sync0:
if(ctrl->rcv_data == 0x88){
packet->cur_sum = 0;
packet->data_len = 0;
ctrl->status_isr &= ~(STATUS_CHKSUM | STATUS_BUSY);
packet->state = state_sync1;
}
break;
case state_sync1:
if(ctrl->rcv_data == 0x33){
packet->state = state_cmd;
} else {
packet->state = state_sync0;
ctrl->status_isr |= STATUS_ERR0;
}
break;
case state_cmd:
packet->cmd = ctrl->rcv_data;
packet->state = state_compr;
break;
case state_compr:
packet->compr = ctrl->rcv_data;
packet->state = state_len_low;
break;
case state_len_low:
packet->cur_len = ctrl->rcv_data;
packet->state = state_len_high;
break;
case state_len_high:
packet->cur_len |= (ctrl->rcv_data << 8);
if(likely(packet->cur_len <= sizeof(packet->data))){
if(packet->cur_len > 0){
packet->state = state_data;
} else {
packet->state = state_chk_low;
}
} else {
/* Bogus data length. We could try inferring the real length
* from the command byte, but since we probably have lost
* synchronisation anyway, we just give up on this packet */
packet->state = state_sync0;
ctrl->status_isr |= STATUS_ERR0;
}
break;
case state_data:
packet->data[packet->data_len++] = ctrl->rcv_data;
--packet->cur_len;
if(unlikely(packet->cur_len == 0)){
packet->state = state_chk_low;
}
break;
case state_chk_low:
packet->chk_sum = ctrl->rcv_data;
packet->state = state_chk_high;
break;
case state_chk_high:
packet->chk_sum |= (ctrl->rcv_data << 8);
/* We need to send the ACK byte next. */
ctrl->snd_data = 0x81;
packet->state = state_ack;
break;
case state_ack:
/* Packet is almost complete, we need to send out the checksum */
/* in the next byte. Check for errors and update ISR status */
/* accordingly. If everything checks out, try copying packet */
/* to transfer ring. */
if(unlikely(packet->chk_sum != packet->cur_sum)){
ctrl->status_isr |= (STATUS_CHKSUM | STATUS_ERR0);
} else {
switch(packet->cmd){
case cmd_data:
/* We expect to always receive complete bands (20 tiles) */
/* of image data. */
if(packet->data_len % 40 == 0)
{
ctrl->status_isr |= STATUS_UNPROC;
} else {
ctrl->status_isr |= STATUS_ERR0;
}
break;
case cmd_print:
if(packet->data_len == PR_PARAM_SIZE){
ctrl->status_isr &= ~STATUS_UNPROC;
ctrl->status_isr |= STATUS_FULL;
} else {
ctrl->status_isr |= STATUS_ERR0;
}
break;
case cmd_inquiry:
break;
case cmd_init:
case cmd_break:
ctrl->status_isr = 0;
break;
}
if(ctrl->packets[ctrl->wr_idx].owner == own_isr){
memcpy(&(ctrl->packets[ctrl->wr_idx]), packet, sizeof(*packet));
} else {
/* Protocol task is too slow, signal busy error. */
ctrl->status_isr |= (STATUS_ERR0 | STATUS_BUSY);
}
}
/* Send combined error status of ISR and task */
ctrl->snd_data = (ctrl->status_isr | ctrl->status_task);
packet->state = state_status;
break;
case state_status:
/* Status byte has been sent out, packet transfer is complete. */
/* Hand packet over to protocol task, unless we had a checksum */
/* error or packet ring was full. */
if(likely((ctrl->status_isr & (STATUS_CHKSUM | STATUS_BUSY)) == 0)){
mb();
ctrl->packets[ctrl->wr_idx].owner = own_task;
mb();
xSemaphoreGiveFromISR(ctrl->packet_done, &task_woken);
if(task_woken){
portYIELD_FROM_ISR();
}
++ctrl->wr_idx;
ctrl->wr_idx %= ARRAY_SIZE(ctrl->packets);
}
/* Get ready to start reception of next packet */
packet->state = state_sync0;
ctrl->snd_data = 0;
break;
default:
/* Should never be reached. */
packet->state = state_sync0;
ctrl->status_isr |= STATUS_ERR0;
break;
}
done:
return;
}
void IRAM_ATTR packet_proto_task(void *pvParameters)
{
struct pr_packet *packet;
struct pr_data *data = NULL;
ESP_LOGI(TAG, "Packet proto task started.");
while(1) {
/* Make sure we have an image data buffer ready before we accept */
/* any command packages from the ISR. */
if(data == NULL){
data = calloc(1, sizeof(*data));
if(data == NULL){
ESP_LOGE(TAG, "Out of memory");
ctrl.status_task = STATUS_BUSY;
continue;
}
}
/* If we have a completed image buffer, try handing it to the main */
/* task for rendering and saving to flash. */
if(data->printed != 0){
if(xQueueSend(img_queue, &data, 0) != pdTRUE){
ctrl.status_task |= STATUS_BUSY;
++data->busy_cnt;
if(data->busy_cnt < 5){
/* Give main task a chance to process the queue. */
vTaskDelay(portTICK_PERIOD_MS);
} else {
/* Give up and signal an error after five tries. */
memset(data, 0x0, sizeof(*data));
ctrl.status_task |= STATUS_ERR0;
}
} else {
/* Data buffer belongs to main task now. We will allocate */
/* a new one on next loop iteration. */
data = NULL;
ctrl.status_task &= ~STATUS_BUSY;
}
/* Restart loop for either retrying this buffer or allocating */
/* a new one. */
continue;
}
/* We have a non-completed data buffer now. Remove busy signal and */
/* wait for command packet from ISR. */
ctrl.status_task &= ~STATUS_BUSY;
packet = &(ctrl.packets[ctrl.rd_idx]);
if(packet->owner == own_isr){
xSemaphoreTake(ctrl.packet_done, portMAX_DELAY);
}
/* No new packet availabe? Restart loop. */
if(packet->owner == own_isr){
continue;
}
ESP_LOGD(TAG, "Got packet: cmd: 0x%02x status_isr: 0x%02x status_task: 0x%02x",
packet->cmd, ctrl.status_isr, ctrl.status_task);
switch(packet->cmd){
case cmd_data:
/* We expect to always receive complete bands (20 tiles) */
/* of image data. Copy data payload into the image buffer */
/* and update the current offset. */
ESP_LOGD(TAG, "cmd_data: len: 0x%x", packet->data_len);
if( (packet->data_len % 40 == 0)
&& (data->data_len + packet->data_len <= sizeof(data->data)))
{
memcpy(&(data->data[data->data_len]),
packet->data,
packet->data_len);
data->data_len += packet->data_len;
ctrl.status_task |= STATUS_UNPROC;
} else {
ESP_LOGE(TAG, "Invalid data buffer length: 0x%x",
packet->data_len);
ctrl.status_task |= STATUS_ERR0;
}
break;
case cmd_print:
/* Image data transfer is complete. Prepare to hand data buffer */
/* over to main task. */
ESP_LOGD(TAG, "cmd_print: len: 0x%x", packet->data_len);
if(packet->data_len == sizeof(data->params)){
memcpy(data->params, packet->data, packet->data_len);
ctrl.status_task &= ~STATUS_UNPROC;
ctrl.status_task |= STATUS_FULL;
data->busy_cnt = 0;
data->printed = 1;
} else {
ESP_LOGE(TAG, "Parameter buffer overrun");
ctrl.status_task |= STATUS_ERR0;
}
break;
case cmd_inquiry:
/* Status inquiry is handled completely in ISR. */
ESP_LOGD(TAG, "cmd_inquiry: len: 0x%x", packet->data_len);
break;
case cmd_init:
case cmd_break:
/* Hard reset the whole transaction. This is the only way to */
/* clear possible error states from the task's status indicator. */
ESP_LOGD(TAG, "cmd_init/break");
memset(data, 0x0, sizeof(*data));
ctrl.status_task = 0;
break;
}
/* Hand ownership of command packet back to ISR, move read index */
/* to next element in ring. */
mb();
packet->owner = own_isr;
mb();
++ctrl.rd_idx;
ctrl.rd_idx %= ARRAY_SIZE(ctrl.packets);
}
}
void app_main()
{
esp_err_t result;
gpio_config_t io_cfg;
struct pr_data *data = NULL;
// Initialise NVS
result = nvs_flash_init();
if( result == ESP_ERR_NVS_NO_FREE_PAGES
|| result == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
result = nvs_flash_init();
}
ESP_ERROR_CHECK(result);
memset(&ctrl, 0x0, sizeof(ctrl));
memset(&io_cfg, 0x0, sizeof(io_cfg));
io_cfg.intr_type = GPIO_INTR_ANYEDGE;
io_cfg.mode = GPIO_MODE_INPUT;
io_cfg.pull_up_en = 0;
io_cfg.pin_bit_mask = (1u << GPIO_SCLK);
result = gpio_config(&io_cfg);
if(result != ESP_OK){
ESP_LOGE(TAG, "gpio_config() failed for GPIO_SCLK");
goto err_out;
}
io_cfg.intr_type = GPIO_INTR_DISABLE;
io_cfg.mode = GPIO_MODE_INPUT;
io_cfg.pull_up_en = 0;
io_cfg.pin_bit_mask = (1u << GPIO_MOSI);
result = gpio_config(&io_cfg);
if(result != ESP_OK){
ESP_LOGE(TAG, "gpio_config() failed for GPIO_MOSI");
goto err_out;
}
io_cfg.intr_type = GPIO_INTR_DISABLE;
io_cfg.mode = GPIO_MODE_OUTPUT_OD;
io_cfg.pull_up_en = 0;
io_cfg.pin_bit_mask = (1u << GPIO_MISO);
result = gpio_config(&io_cfg);
if(result != ESP_OK){
ESP_LOGE(TAG, "gpio_config() failed for GPIO_MISO");
goto err_out;
}
memset(&ctrl, 0x0, sizeof(ctrl));
ctrl.packet_done = xSemaphoreCreateBinary();
if(ctrl.packet_done == NULL){
ESP_LOGE(TAG, "xSemaphoreCreateBinary() failed");
goto err_out;
}
result = init_spiffs();
if(result != ESP_OK){
ESP_LOGE(TAG, "[%s] init_spiffs() failed", __func__);
goto err_out;
}
result = gpio_install_isr_service(0);
if(result != ESP_OK){
ESP_LOGE(TAG, "[%s] gpio_install_isr_service() failed", __func__);
goto err_out;
}
result = gpio_isr_handler_add(GPIO_SCLK, sclk_isr_handler, &ctrl);
if(result != ESP_OK){
ESP_LOGE(TAG, "[%s] gpio_isr_handler_add() failed", __func__);
goto err_out;
}
img_queue = xQueueCreate(2, sizeof(struct pr_data *));
if(img_queue == NULL){
ESP_LOGE(TAG, "[%s] Creating packet queue failed.", __func__);
goto err_out;
}
#if 1
wifi_init_softap();
http_srv_init();
#endif
xTaskCreate(&packet_proto_task, "packet_proto_task", 3072, NULL, 2, NULL);
ESP_LOGI(TAG, "Entering main loop");
while(1){
if(xQueueReceive(img_queue, &data, portMAX_DELAY) == pdTRUE){
save_data(data);
free(data);
}
}
err_out:
return;
}