mirror of
https://github.com/gusmanb/logicanalyzer.git
synced 2024-12-22 01:39:30 +00:00
659 lines
21 KiB
C
659 lines
21 KiB
C
#include "LogicAnalyzer_Board_Settings.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "pico/stdlib.h"
|
|
#include "hardware/dma.h"
|
|
#include "hardware/pio.h"
|
|
#include "hardware/clocks.h"
|
|
#include "hardware/flash.h"
|
|
#include "pico/multicore.h"
|
|
#include "LogicAnalyzer.pio.h"
|
|
#include "LogicAnalyzer_Structs.h"
|
|
#include "tusb.h"
|
|
|
|
#ifdef WS2812_LED
|
|
#include "LogicAnalyzer_W2812.h"
|
|
#endif
|
|
|
|
#if defined (CYGW_LED) || defined(USE_CYGW_WIFI)
|
|
|
|
#include "pico/cyw43_arch.h"
|
|
|
|
#ifdef USE_CYGW_WIFI
|
|
|
|
#include "Event_Machine.h"
|
|
#include "Shared_Buffers.h"
|
|
#include "LogicAnalyzer_WiFi.h"
|
|
#include "hardware/regs/usb.h"
|
|
#include "hardware/structs/usb.h"
|
|
|
|
bool usbDisabled = false;
|
|
bool cywReady = false;
|
|
bool skipWiFiData = false;
|
|
bool dataFromWiFi = false;
|
|
EVENT_FROM_WIFI wifiEventBuffer;
|
|
WIFI_SETTINGS_REQUEST* wReq;
|
|
|
|
#define MULTICORE_LOCKOUT_TIMEOUT (uint64_t)10 * 365 * 24 * 60 * 60 * 1000 * 1000
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#if defined (GPIO_LED)
|
|
#define INIT_LED() {\
|
|
gpio_init(LED_IO); \
|
|
gpio_set_dir(LED_IO, GPIO_OUT); \
|
|
}
|
|
#define LED_ON() gpio_put(LED_IO, 1)
|
|
#define LED_OFF() gpio_put(LED_IO, 0)
|
|
#elif defined (CYGW_LED)
|
|
|
|
#define INIT_LED() { }
|
|
|
|
#ifdef USE_CYGW_WIFI
|
|
#define LED_ON() {\
|
|
EVENT_FROM_FRONTEND lonEvt;\
|
|
lonEvt.event = LED_ON;\
|
|
event_push(&frontendToWifi, &lonEvt);\
|
|
}
|
|
|
|
#define LED_OFF() {\
|
|
EVENT_FROM_FRONTEND loffEvt;\
|
|
loffEvt.event = LED_OFF;\
|
|
event_push(&frontendToWifi, &loffEvt);\
|
|
}
|
|
#else
|
|
#define LED_ON() cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1)
|
|
#define LED_OFF() cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0)
|
|
#endif
|
|
|
|
#elif defined (WS2812_LED)
|
|
#define INIT_LED() init_rgb()
|
|
#define LED_ON() send_rgb(0,32,0)
|
|
#define LED_OFF() send_rgb(0,0,32)
|
|
#endif
|
|
|
|
//Buffer used to store received data
|
|
uint8_t messageBuffer[128];
|
|
//Position in the buffer
|
|
uint8_t bufferPos = 0;
|
|
//Capture status
|
|
bool capturing = false;
|
|
//Capture request pointer
|
|
CAPTURE_REQUEST* req;
|
|
|
|
#ifdef USE_CYGW_WIFI
|
|
|
|
/// @brief Stores a new WiFi configuration in the flash of the device
|
|
/// @param settings Settings to store
|
|
void storeSettings(WIFI_SETTINGS* settings)
|
|
{
|
|
uint8_t buffer[FLASH_PAGE_SIZE];
|
|
memcpy(buffer, settings, sizeof(WIFI_SETTINGS));
|
|
//multicore_lockout_start_blocking ();
|
|
multicore_lockout_start_timeout_us(MULTICORE_LOCKOUT_TIMEOUT);
|
|
|
|
uint32_t intStatus = save_and_disable_interrupts();
|
|
|
|
flash_range_erase(FLASH_SETTINGS_OFFSET, FLASH_SECTOR_SIZE);
|
|
|
|
for(int buc = 0; buc < 1000; buc++)
|
|
{
|
|
asm("nop");
|
|
asm("nop");
|
|
asm("nop");
|
|
asm("nop");
|
|
asm("nop");
|
|
}
|
|
|
|
flash_range_program(FLASH_SETTINGS_OFFSET, buffer, FLASH_PAGE_SIZE);
|
|
|
|
for(int buc = 0; buc < 1000; buc++)
|
|
{
|
|
asm("nop");
|
|
asm("nop");
|
|
asm("nop");
|
|
asm("nop");
|
|
asm("nop");
|
|
}
|
|
|
|
restore_interrupts(intStatus);
|
|
|
|
bool unlocked = false;
|
|
|
|
do {
|
|
unlocked = multicore_lockout_end_timeout_us(MULTICORE_LOCKOUT_TIMEOUT);
|
|
} while(!unlocked);
|
|
|
|
sleep_ms(500);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/// @brief Sends a response message to the host application in string mode
|
|
/// @param response The message to be sent (null terminated)
|
|
/// @param toWiFi If true the message is sent to a WiFi endpoint, else to the USB connection through STDIO
|
|
void sendResponse(const char* response, bool toWiFi)
|
|
{
|
|
#ifdef USE_CYGW_WIFI
|
|
if(toWiFi)
|
|
{
|
|
EVENT_FROM_FRONTEND evt;
|
|
evt.event = SEND_DATA;
|
|
evt.dataLength = strlen(response);
|
|
memset(evt.data, 0, 32);
|
|
memcpy(evt.data, response, evt.dataLength);
|
|
event_push(&frontendToWifi, &evt);
|
|
}
|
|
else
|
|
#endif
|
|
printf(response);
|
|
}
|
|
|
|
/// @brief Transfer a buffer of data through USB using the TinyUSB CDC functions
|
|
/// @param data Buffer of data to transfer
|
|
/// @param len Length of the buffer
|
|
void cdc_transfer(unsigned char* data, int len)
|
|
{
|
|
|
|
int left = len;
|
|
int pos = 0;
|
|
|
|
while(left > 0)
|
|
{
|
|
int avail = (int) tud_cdc_write_available();
|
|
|
|
if(avail > left)
|
|
avail = left;
|
|
|
|
if(avail)
|
|
{
|
|
int transferred = (int) tud_cdc_write(data + pos, avail);
|
|
tud_task();
|
|
tud_cdc_write_flush();
|
|
|
|
pos += transferred;
|
|
left -= transferred;
|
|
}
|
|
else
|
|
{
|
|
tud_task();
|
|
tud_cdc_write_flush();
|
|
if (!tud_cdc_connected())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @brief Processes data received from the host application
|
|
/// @param data The received data
|
|
/// @param length Length of the data
|
|
/// @param fromWiFi If true the message comes from a WiFi connection
|
|
void processData(uint8_t* data, uint length, bool fromWiFi)
|
|
{
|
|
for(uint pos = 0; pos < length; pos++)
|
|
{
|
|
//Store char in buffer and increment position
|
|
messageBuffer[bufferPos++] = data[pos];
|
|
|
|
//If we have stored the first byte and it is not 0x55 restart reception
|
|
if(bufferPos == 1 && messageBuffer[0] != 0x55)
|
|
bufferPos = 0;
|
|
else if(bufferPos == 2 && messageBuffer[1] != 0xAA) //If we have stored the second byte and it is not 0xAA restart reception
|
|
bufferPos = 0;
|
|
else if(bufferPos >= 256) //Have we overflowed the buffer? then inform to the host and restart reception
|
|
{
|
|
sendResponse("ERR_MSG_OVERFLOW\n", fromWiFi);
|
|
bufferPos = 0;
|
|
}
|
|
else if(bufferPos > 2) //Try to parse the data
|
|
{
|
|
if(messageBuffer[bufferPos - 2] == 0xAA && messageBuffer[bufferPos - 1] == 0x55) //Do we have the stop condition?
|
|
{
|
|
|
|
//Yes, unescape the buffer,
|
|
int dest = 0;
|
|
|
|
for(int src = 0; src < bufferPos; src++)
|
|
{
|
|
if(messageBuffer[src] == 0xF0)
|
|
{
|
|
messageBuffer[dest] = messageBuffer[src + 1] ^ 0xF0;
|
|
src++;
|
|
}
|
|
else
|
|
messageBuffer[dest] = messageBuffer[src];
|
|
|
|
dest++;
|
|
}
|
|
|
|
switch(messageBuffer[2]) //Check the command we received
|
|
{
|
|
|
|
case 0: //ID request
|
|
|
|
if(bufferPos != 5) //Malformed message?
|
|
sendResponse("ERR_UNKNOWN_MSG\n", fromWiFi);
|
|
else
|
|
sendResponse("LOGIC_ANALYZER_"BOARD_NAME"_"FIRMWARE_VERSION"\n", fromWiFi);
|
|
break;
|
|
|
|
case 1: //Capture request
|
|
|
|
req = (CAPTURE_REQUEST*)&messageBuffer[3]; //Get the request pointer
|
|
|
|
bool started = false;
|
|
|
|
#ifdef SUPPORTS_COMPLEX_TRIGGER
|
|
|
|
if(req->triggerType == 1) //Start complex trigger capture
|
|
started = startCaptureComplex(req->frequency, req->preSamples, req->postSamples, (uint8_t*)&req->channels, req->channelCount, req->trigger, req->count, req->triggerValue, req->captureMode);
|
|
else if(req->triggerType == 2) //start fast trigger capture
|
|
started = startCaptureFast(req->frequency, req->preSamples, req->postSamples, (uint8_t*)&req->channels, req->channelCount, req->trigger, req->count, req->triggerValue, req->captureMode);
|
|
else //Start simple trigger capture
|
|
started = startCaptureSimple(req->frequency, req->preSamples, req->postSamples, req->loopCount, (uint8_t*)&req->channels, req->channelCount, req->trigger, req->inverted, req->captureMode);
|
|
|
|
#else
|
|
|
|
if(req->triggerType == 1 || req->triggerType == 2)
|
|
{
|
|
sendResponse("CAPTURE_ERROR\n", fromWiFi);
|
|
break;
|
|
}
|
|
else
|
|
started = startCaptureSimple(req->frequency, req->preSamples, req->postSamples, req->loopCount, (uint8_t*)&req->channels, req->channelCount, req->trigger, req->inverted, req->captureMode);
|
|
|
|
#endif
|
|
|
|
if(started) //If started successfully inform to the host
|
|
{
|
|
sendResponse("CAPTURE_STARTED\n", fromWiFi);
|
|
capturing = true;
|
|
}
|
|
else
|
|
sendResponse("CAPTURE_ERROR\n", fromWiFi); //Else notify the error
|
|
|
|
break;
|
|
|
|
#ifdef USE_CYGW_WIFI
|
|
|
|
case 2: //Update WiFi settings
|
|
|
|
wReq = (WIFI_SETTINGS_REQUEST*)&messageBuffer[3];
|
|
WIFI_SETTINGS settings;
|
|
memcpy(settings.apName, wReq->apName, 33);
|
|
memcpy(settings.passwd, wReq->passwd, 64);
|
|
memcpy(settings.ipAddress, wReq->ipAddress, 16);
|
|
settings.port = wReq->port;
|
|
|
|
for(int buc = 0; buc < 33; buc++)
|
|
settings.checksum += settings.apName[buc];
|
|
|
|
for(int buc = 0; buc < 64; buc++)
|
|
settings.checksum += settings.passwd[buc];
|
|
|
|
for(int buc = 0; buc < 16; buc++)
|
|
settings.checksum += settings.ipAddress[buc];
|
|
|
|
settings.checksum += settings.port;
|
|
|
|
settings.checksum += 0x0f0f;
|
|
|
|
storeSettings(&settings);
|
|
|
|
wifiSettings = settings;
|
|
|
|
EVENT_FROM_FRONTEND evt;
|
|
evt.event = CONFIG_RECEIVED;
|
|
event_push(&frontendToWifi, &evt);
|
|
|
|
sendResponse("SETTINGS_SAVED\n", fromWiFi);
|
|
|
|
break;
|
|
|
|
case 3: //Read power status
|
|
|
|
if(!fromWiFi)
|
|
sendResponse("ERR_UNSUPPORTED\n", fromWiFi);
|
|
else
|
|
{
|
|
EVENT_FROM_FRONTEND powerEvent;
|
|
powerEvent.event = GET_POWER_STATUS;
|
|
event_push(&frontendToWifi, &powerEvent);
|
|
}
|
|
|
|
break;
|
|
|
|
#else
|
|
|
|
case 2:
|
|
case 3:
|
|
|
|
sendResponse("ERR_UNSUPPORTED\n", fromWiFi);
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
sendResponse("ERR_UNKNOWN_MSG\n", fromWiFi); //Unknown message
|
|
break;
|
|
|
|
}
|
|
|
|
bufferPos = 0; //Reset buffer position
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//PROTOCOL EXPLAINED:
|
|
//
|
|
//The protocol is very basic, it receives binary frames and sends strings terminated by a carriage return.
|
|
//
|
|
//Each binary frame has a start and an end condition, being these two secuences of two bytes:
|
|
// start condition: 0x55 0xAA
|
|
// stop condition: 0xAA 0x55
|
|
//
|
|
//This kind of framing can cause problems if the packets contain the frame condition bytes, there needs to be implemented
|
|
//a scape character to avoid this.The char 0xF0 is used as escape character. Escaping is done by XOR'ing the scape character
|
|
//with the scaped char. For example, if we need to send 0xAA we would send { 0xF0, 0x5A }, which is 0xAA XOR 0xF0 = 0x5A.
|
|
//In case of sending the scape char we would send { 0xF0, 0x00 }.
|
|
//
|
|
//Inside each frame we have a command byte and additional data. Based on the command a binary struct will be deserialized
|
|
//from the buffer. Right now the protocol has only two commands: ID request and capture request. ID request does not
|
|
//have any data, but the capture request has a CAPTURE_REQUEST struct as data.
|
|
}
|
|
|
|
/// @brief Receive and process USB data from the host application
|
|
/// @param skipProcessing If true the received data is not processed (used for cleanup)
|
|
/// @return True if anything is received, false if not
|
|
bool processUSBInput(bool skipProcessing)
|
|
{
|
|
//Try to get char
|
|
uint data = getchar_timeout_us(0);
|
|
|
|
//Timeout? Then leave
|
|
if(data == PICO_ERROR_TIMEOUT)
|
|
return false;
|
|
|
|
uint8_t filteredData = (uint8_t)data;
|
|
|
|
if(!skipProcessing)
|
|
processData(&filteredData, 1, false);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#ifdef USE_CYGW_WIFI
|
|
|
|
/// @brief Purges any pending data in the USB input
|
|
void purgeUSBData()
|
|
{
|
|
while(getchar_timeout_us(0) != PICO_ERROR_TIMEOUT);
|
|
}
|
|
|
|
/// @brief Send a string response with the power status
|
|
/// @param status Status received from the WiFi core
|
|
void sendPowerStatus(POWER_STATUS* status)
|
|
{
|
|
char buffer[32];
|
|
memset(buffer, 0, 32);
|
|
int len = sprintf(buffer, "%.2f", status->vsysVoltage);
|
|
buffer[len++] = '_';
|
|
buffer[len++] = status->vbusConnected ? '1' : '0';
|
|
buffer[len] = '\n';
|
|
sendResponse(buffer, true);
|
|
}
|
|
|
|
/// @brief Callback for the WiFi event queue
|
|
/// @param event Received event
|
|
void wifiEvent(void* event)
|
|
{
|
|
EVENT_FROM_WIFI* wEvent = (EVENT_FROM_WIFI*)event;
|
|
|
|
switch(wEvent->event)
|
|
{
|
|
case CYW_READY:
|
|
cywReady = true;
|
|
break;
|
|
case CONNECTED:
|
|
usbDisabled = true;
|
|
//disableUSB();
|
|
break;
|
|
case DISCONNECTED:
|
|
usbDisabled = false;
|
|
purgeUSBData();
|
|
//enableUSB();
|
|
break;
|
|
case DATA_RECEIVED:
|
|
if(skipWiFiData)
|
|
dataFromWiFi = true;
|
|
else
|
|
processData(wEvent->data, wEvent->dataLength, true);
|
|
break;
|
|
case POWER_STATUS_DATA:
|
|
{
|
|
POWER_STATUS status;
|
|
memcpy(&status, wEvent->data, sizeof(POWER_STATUS));
|
|
sendPowerStatus(&status);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// @brief Receives and processes input from the host application (when connected through WiFi)
|
|
/// @param skipProcessing /// @param skipProcessing If true the received data is not processed (used for cleanup)
|
|
/// @return True if anything is received, false if not
|
|
bool processWiFiInput(bool skipProcessing)
|
|
{
|
|
bool res = event_has_events(&wifiToFrontend);
|
|
|
|
if(skipProcessing)
|
|
{
|
|
skipWiFiData = true;
|
|
dataFromWiFi = false;
|
|
}
|
|
|
|
event_process_queue(&wifiToFrontend, &wifiEventBuffer, 8);
|
|
|
|
skipWiFiData = false;
|
|
|
|
return dataFromWiFi;
|
|
}
|
|
|
|
#endif
|
|
|
|
/// @brief Process input data from the host application if it is available
|
|
void processInput()
|
|
{
|
|
#ifdef USE_CYGW_WIFI
|
|
if(!usbDisabled)
|
|
processUSBInput(false);
|
|
|
|
processWiFiInput(false);
|
|
#else
|
|
processUSBInput(false);
|
|
#endif
|
|
}
|
|
|
|
/// @brief Processes input data from the host application to check if there is any cancel capture request
|
|
/// @return True if there was input data
|
|
bool processCancel()
|
|
{
|
|
#ifdef USE_CYGW_WIFI
|
|
if(!usbDisabled)
|
|
if(processUSBInput(true))
|
|
return true;
|
|
|
|
return processWiFiInput(true);
|
|
#else
|
|
return processUSBInput(true);
|
|
#endif
|
|
}
|
|
|
|
/// @brief Main app loop
|
|
/// @return Exit code
|
|
int main()
|
|
{
|
|
//Overclock Powerrrr!
|
|
set_sys_clock_khz(200000, true);
|
|
|
|
//Initialize USB stdio
|
|
stdio_init_all();
|
|
|
|
#if defined (BUILD_PICO_W)
|
|
cyw43_arch_init();
|
|
#elif defined (BUILD_PICO_W_WIFI)
|
|
event_machine_init(&wifiToFrontend, wifiEvent, sizeof(EVENT_FROM_WIFI), 8);
|
|
multicore_launch_core1(runWiFiCore);
|
|
while(!cywReady)
|
|
event_process_queue(&wifiToFrontend, &wifiEventBuffer, 1);
|
|
#endif
|
|
|
|
//A bit of delay, if the program tries to send data before Windows has identified the device it may crash
|
|
sleep_ms(1000);
|
|
|
|
//Clear message buffer
|
|
memset(messageBuffer, 0, 128);
|
|
|
|
//Configure led
|
|
INIT_LED();
|
|
LED_ON();
|
|
|
|
while(1)
|
|
{
|
|
//Are we capturing?
|
|
if(capturing)
|
|
{
|
|
//Is the PIO units still working?
|
|
if(!IsCapturing())
|
|
{
|
|
//Retrieve the capture buffer and get info about it.
|
|
uint32_t length, first;
|
|
CHANNEL_MODE mode;
|
|
uint8_t* buffer = GetBuffer(&length, &first, &mode);
|
|
|
|
//Send the data to the host
|
|
uint8_t* lengthPointer = (uint8_t*)&length;
|
|
|
|
//Send capture length
|
|
sleep_ms(100);
|
|
|
|
#ifdef USE_CYGW_WIFI
|
|
|
|
if(usbDisabled)
|
|
{
|
|
EVENT_FROM_FRONTEND evt;
|
|
evt.event = SEND_DATA;
|
|
evt.dataLength = 4;
|
|
memcpy(evt.data, lengthPointer, 4);
|
|
event_push(&frontendToWifi, &evt);
|
|
}
|
|
else
|
|
cdc_transfer(lengthPointer, 4);
|
|
|
|
#else
|
|
cdc_transfer(lengthPointer, 4);
|
|
#endif
|
|
|
|
sleep_ms(100);
|
|
|
|
//Tanslate sample numbers to byte indexes, makes easier to send data
|
|
switch(mode)
|
|
{
|
|
case MODE_16_CHANNEL:
|
|
length *= 2;
|
|
first *= 2;
|
|
break;
|
|
case MODE_24_CHANNEL:
|
|
length *= 4;
|
|
first *= 4;
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_CYGW_WIFI
|
|
|
|
//Send the samples
|
|
if(usbDisabled)
|
|
{
|
|
EVENT_FROM_FRONTEND evt;
|
|
evt.event = SEND_DATA;
|
|
|
|
int pos = 0;
|
|
int filledData;
|
|
while(pos < length)
|
|
{
|
|
filledData = 0;
|
|
while(pos < length && filledData < 32)
|
|
{
|
|
evt.data[filledData] = buffer[first++];
|
|
|
|
if(first >= 131072)
|
|
first = 0;
|
|
|
|
pos++;
|
|
filledData++;
|
|
}
|
|
|
|
evt.dataLength = filledData;
|
|
event_push(&frontendToWifi, &evt);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(first + length > CAPTURE_BUFFER_SIZE)
|
|
{
|
|
cdc_transfer(buffer + first, CAPTURE_BUFFER_SIZE - first);
|
|
cdc_transfer(buffer, (first + length) - CAPTURE_BUFFER_SIZE);
|
|
}
|
|
else
|
|
cdc_transfer(buffer + first, length);
|
|
}
|
|
#else
|
|
|
|
if(first + length > CAPTURE_BUFFER_SIZE)
|
|
{
|
|
cdc_transfer(buffer + first, CAPTURE_BUFFER_SIZE - first);
|
|
cdc_transfer(buffer, (first + length) - CAPTURE_BUFFER_SIZE);
|
|
}
|
|
else
|
|
cdc_transfer(buffer + first, length);
|
|
|
|
#endif
|
|
//Done!
|
|
capturing = false;
|
|
}
|
|
else
|
|
{
|
|
LED_OFF();
|
|
sleep_ms(1000);
|
|
|
|
//Check for cancel request
|
|
if(processCancel())
|
|
{
|
|
//Stop capture
|
|
stopCapture();
|
|
capturing = false;
|
|
LED_ON();
|
|
}
|
|
else
|
|
{
|
|
LED_ON();
|
|
#ifdef SUPPORTS_COMPLEX_TRIGGER
|
|
check_fast_interrupt();
|
|
#endif
|
|
sleep_ms(1000);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
processInput(); //Read incomming data
|
|
}
|
|
|
|
return 0;
|
|
} |