From ad4621cb2ea7b1cd1379a69c244f45b2574cccdf Mon Sep 17 00:00:00 2001 From: Kelvin Ly Date: Sat, 16 Apr 2022 08:39:04 -0400 Subject: [PATCH] Start work on SPI testing framework --- .gitignore | 1 + CMakeLists.txt | 25 ++++++++ boards/walkie_talkies.h | 6 ++ host/CMakeLists.txt | 10 ++++ src/blink_test.c | 10 +++- src/spi_parser.h | 128 ++++++++++++++++++++++++++++++++++++++++ src/spi_parser_test.c | 107 +++++++++++++++++++++++++++++++++ src/spi_test.c | 87 +++++++++++++++++++++++++++ 8 files changed, 372 insertions(+), 2 deletions(-) create mode 100644 host/CMakeLists.txt create mode 100644 src/spi_parser.h create mode 100644 src/spi_parser_test.c create mode 100644 src/spi_test.c diff --git a/.gitignore b/.gitignore index 9662e1f..ab9d5d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *-backups/ build/ +hostbuild/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 069beea..9ec2e81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,3 +23,28 @@ pico_enable_stdio_usb(uart_test 1) pico_enable_stdio_uart(uart_test 0) pico_add_extra_outputs(uart_test) + +add_executable(blink_test + src/blink_test.c + ) + +target_link_libraries(blink_test pico_stdlib) + +pico_enable_stdio_usb(blink_test 1) +pico_enable_stdio_uart(blink_test 0) + +pico_add_extra_outputs(blink_test) + +add_executable(spi_test + src/spi_test.c + ) + +target_link_libraries(spi_test + pico_stdlib + hardware_spi + ) + +pico_enable_stdio_usb(spi_test 1) +pico_enable_stdio_uart(spi_test 0) + +pico_add_extra_outputs(spi_test) diff --git a/boards/walkie_talkies.h b/boards/walkie_talkies.h index 8c27e6c..1ea4ad9 100644 --- a/boards/walkie_talkies.h +++ b/boards/walkie_talkies.h @@ -3,6 +3,12 @@ #define PICO_FLASH_SIZE_BYTES (16*1024*1024) +#define PICO_DEFAULT_SPI 0 +#define PICO_DEFAULT_SPI_SCK_PIN 1 +#define PICO_DEFAULT_SPI_TX_PIN 3 +#define PICO_DEFAULT_SPI_RX_PIN 0 +#define PICO_DEFAULT_SPI_CSN_PIN 2 + #include "boards/pico.h" #undef PICO_BOOT_STAGE2_CHOOSE_W25Q080 diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt new file mode 100644 index 0000000..0987047 --- /dev/null +++ b/host/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.12) + +project(walkie_talkies_host) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +add_executable(spi_parse_test + ../src/spi_parser_test.c + ) diff --git a/src/blink_test.c b/src/blink_test.c index 61d36c7..6d19544 100644 --- a/src/blink_test.c +++ b/src/blink_test.c @@ -1,11 +1,17 @@ #include #include "pico/stdlib.h" +#define TEST_GPIO 8 + int main() { stdio_init_all(); + gpio_init(TEST_GPIO); + gpio_set_dir(TEST_GPIO, GPIO_OUT); + bool val = true; while (true) { - printf("test!\n"); - sleep_ms(250); + gpio_put(TEST_GPIO, val); + val = !val; + sleep_ms(25); } return 0; } diff --git a/src/spi_parser.h b/src/spi_parser.h new file mode 100644 index 0000000..0b2d84b --- /dev/null +++ b/src/spi_parser.h @@ -0,0 +1,128 @@ +#ifndef SPI_PARSER_H_ +#define SPI_PARSER_H_ + +#include + +// probably should include stdint but I'm not sure how that'd +// interact with Pico SDK's stuff, so I'll leave it out + +static int maybe_nibble(char c) { + if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + return -1; +} + + +static void spi_register_write(uint16_t address, const uint8_t *buf, uint16_t len); +static void spi_register_read(uint16_t address, uint8_t *buf, uint16_t len); + +static void parse_and_execute_cmd(const uint8_t* s, int c_len) { + if (c_len == 0) { + return; + } + + int is_write = (s[0] == 'w'); + int is_read = (s[0] == 'r'); + if (!is_write && !is_read) { + puts("invalid cmd; no r/w"); + return; + } + + int offset = 1; + + uint16_t addr = 0; + int len = 0; + uint8_t buf[64]; + + for (int i = 0; i < 4 && (i + 1) < c_len; i++) { + int mn = maybe_nibble(s[i + offset]); + if (mn < 0) { + puts("invalid cmd; bad addr"); + return; + } + addr = (addr << 4) | (mn & 0xf); + } + + offset += 4; + offset += 1; // skip a character, some kind of whitespace is allowed + // variable length entry, read until + int readlen = 0; + int mn = maybe_nibble(s[offset]); + while ((mn >= 0) && (offset < c_len)) { + len = (len << 4) | (mn & 0xf); + ++offset; + mn = maybe_nibble(s[offset]); + } + + if (len > sizeof(buf)) { + len = sizeof(buf); + } + + const uint8_t tohex[] = "0123456789abcdef"; + + if (is_read) { + // we're good, time to execute + spi_register_read(addr, buf, len); + uint8_t output_str[3*sizeof(buf) + 1]; + for (int i = 0; i < len; i++) { + output_str[3*i] = tohex[(buf[i] >> 4) & 0xf]; + output_str[3*i + 1] = tohex[buf[i] & 0xf]; + output_str[3*i + 2] = ' '; + } + output_str[3*len - 1] = '\n'; + output_str[3*len] = 0; + puts(output_str); + return; + } + ++offset; // skip whitespace character + + if (is_write) { + // parse write data one nibble at a time until it reaches the end + // or len characters have been read + int nibble_count = 0; + int cur_byte = 0; + for (; (nibble_count < 2*len) && (offset < c_len); offset++) { + int mn = maybe_nibble(s[offset]); + if (mn >= 0) { + cur_byte = (cur_byte << 4) | (mn & 0xf); + ++nibble_count; + if ((nibble_count % 2) == 0) { + buf[(nibble_count/2) - 1] = cur_byte & 0xff; + } + } + } + + if (nibble_count < 2*len) { + puts("parse error: not enough data for write\n"); + return; + } + + spi_register_write(addr, buf, len); + uint8_t output_s[3*sizeof(buf) + 4 + 9]; + output_s[0] = 'w'; + output_s[1] = ' '; + for (int i = 0; i < 4; i++) { + output_s[2+i] = tohex[(addr >> (4*(3-i))) & 0xf]; + } + output_s[2+4] = ' '; + + uint8_t* output_str = output_s + 7; + for (int i = 0; i < len; i++) { + output_str[3*i] = tohex[(buf[i] >> 4) & 0xf]; + output_str[3*i + 1] = tohex[buf[i] & 0xf]; + output_str[3*i + 2] = ' '; + } + output_str[3*len - 1] = '\n'; + output_str[3*len] = 0; + + puts(output_s); + return; + } +} + + +#endif // SPI_PARSER_H_ diff --git a/src/spi_parser_test.c b/src/spi_parser_test.c new file mode 100644 index 0000000..02f1640 --- /dev/null +++ b/src/spi_parser_test.c @@ -0,0 +1,107 @@ +#include +#include +#include + +#include "spi_parser.h" + +int reg_written = 0; +int reg_read = 0; + +int reg_addr = 0; +int reg_len = 0; +uint8_t reg_buf[64]; + +static void spi_register_write( + uint16_t address, const uint8_t *buf, uint16_t len) { + reg_written = 1; + reg_addr = address; + reg_len = len; + memcpy(reg_buf, buf, len); +} + +static void spi_register_read(uint16_t address, uint8_t *buf, uint16_t len) { + reg_read = 1; + reg_addr = address; + reg_len = len; +} + +static void reset_reg() { + reg_written = 0; + reg_read = 0; + reg_addr = 0; + reg_len = 0; +} + +struct test_case { + const uint8_t* s; + int s_len; + int is_read; + int is_write; + int addr; + int len; + const uint8_t* expected; +}; + +static int check_reg(struct test_case* tc) { + if (tc->is_read) { + return reg_read && (reg_addr == tc->addr) && (reg_len == tc->len); + } else if (tc->is_write) { + return reg_written && (reg_addr == tc->addr) && (reg_len == tc->len) && (memcmp(reg_buf, tc->expected, tc->len) == 0); + } + return !reg_read && !reg_written; +} + +static void dump_expected(struct test_case* tc) { + if (tc->is_read && !reg_read) { + printf("read bit not set, "); + } else if (tc->is_write && !reg_written) { + printf("write bit not set, "); + } else { + if (reg_read) { + printf("read bit SET, should\'nt be \n"); + return; + } + if (reg_written) { + printf("read bit SET, should\'nt be \n"); + return; + } + } + if (tc->addr != reg_addr) { + printf("got addr %04x, expected %04x, ", reg_addr, tc->addr); + } + if (tc->len != reg_len) { + printf("got len %04x, expected %04x, ", reg_len, tc->len); + } + printf("\n"); +} + +int main() { + const uint8_t test0[] = "r0102 1\n"; + const uint8_t test1[] = "r1234 1a\n"; + const uint8_t test2[] = "wffe7 3 11 22 33\n"; + const uint8_t test2_buf[] = {0x11, 0x22, 0x33}; + const uint8_t test3[] = "\n"; + const uint8_t test4[] = "w1ae7 8 1122 3344 ffee e0 f5\n"; + const uint8_t test4_buf[] = {0x11, 0x22, 0x33, 0x44, 0xff, 0xee, 0xe0, 0xf5}; + struct test_case cases[] = { + {test0, sizeof(test0), 1, 0, 0x0102, 1, NULL}, + {test1, sizeof(test1), 1, 0, 0x1234, 0x1a, NULL}, + {test2, sizeof(test2), 0, 1, 0xffe7, 0x3, test2_buf}, + {test3, sizeof(test3), 0, 0, 0, 0, NULL}, + {test4, sizeof(test4), 0, 1, 0x1ae7, 8, test4_buf}, + }; + + for (int i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) { + reset_reg(); + parse_and_execute_cmd(cases[i].s, cases[i].s_len); + if (!check_reg(&cases[i])) { + printf("E: test failed\n"); + dump_expected(&cases[i]); + return -1; + } else { + printf("I: test %d passed\n", i); + } + } + + return 0; +} diff --git a/src/spi_test.c b/src/spi_test.c new file mode 100644 index 0000000..d56d6f6 --- /dev/null +++ b/src/spi_test.c @@ -0,0 +1,87 @@ +#include +#include "pico/stdlib.h" + +#include "hardware/spi.h" + +#include "spi_parser.h" + +#define AT86_RSTN 7 + +static void cs_select() { + asm volatile("nop \n nop \n nop"); + gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 0); // Active low + asm volatile("nop \n nop \n nop"); +} + +static void cs_deselect() { + asm volatile("nop \n nop \n nop"); + gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 1); + asm volatile("nop \n nop \n nop"); +} + +static void spi_register_write(uint16_t address, const uint8_t *buf, uint16_t len) { + uint8_t cmd[2] = { address >> 8, address }; + cmd[0] &= ~(3 << 6); // mask out top two bits + // to set MODE to read + cs_select(); + spi_write_blocking(PICO_DEFAULT_SPI, cmd, 2); + spi_write_blocking(PICO_DEFAULT_SPI, buf, len); + cs_deselect(); +} + +static void spi_register_read(uint16_t address, uint8_t *buf, uint16_t len) { + uint8_t cmd[2] = { address >> 8, address }; + cmd[0] &= ~(3 << 6); // mask out top two bits + cmd[0] |= (2 << 6); + // set MODE[0] to set to write + cs_select(); + spi_write_blocking(PICO_DEFAULT_SPI, cmd, 2); + spi_read_blocking(PICO_DEFAULT_SPI, 0, buf, len); + cs_deselect(); +} + +int main() { + stdio_init_all(); + // 10 MHz + spi_init(PICO_DEFAULT_SPI, 10*1000*1000); + gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI); + gpio_set_function(PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI); + gpio_set_function(PICO_DEFAULT_SPI_TX_PIN, GPIO_FUNC_SPI); + + gpio_init(PICO_DEFAULT_SPI_CSN_PIN); + gpio_set_dir(PICO_DEFAULT_SPI_CSN_PIN, GPIO_OUT); + gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 1); + + gpio_set_dir(AT86_RSTN, GPIO_OUT); + gpio_put(AT86_RSTN, 1); + + uint8_t cmd[64]; + int cmd_idx = 0; + while (true) { + if (stdio_usb_connected()) { + // UART parse loop + int maybe_ch = getchar_timeout_us(1000); + if (maybe_ch != PICO_ERROR_TIMEOUT) { + if (maybe_ch == 0x0a) { + cmd[cmd_idx] = 0; + putchar(maybe_ch); + // process command + parse_and_execute_cmd(cmd, cmd_idx); + cmd_idx = 0; + } else if (maybe_ch == 0x1b) { + putchar('^'); + putchar('\n'); + cmd_idx = 0; + } else { + putchar(maybe_ch); + cmd[cmd_idx] = maybe_ch; + if (cmd_idx < sizeof(cmd)-1) { + ++cmd_idx; + } + } + } + } else { + sleep_ms(10); + } + } +}