diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6ec7710..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "User"] - path = User - url = https://git.ondrovo.com/gex/gex-core.git diff --git a/GexUnits/1wire/_ow_api.c b/GexUnits/1wire/_ow_api.c new file mode 100644 index 0000000..6c9b94b --- /dev/null +++ b/GexUnits/1wire/_ow_api.c @@ -0,0 +1,129 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_1wire.h" + +// 1WIRE master +#define OW_INTERNAL +#include "_ow_internal.h" +#include "_ow_commands.h" +#include "_ow_low_level.h" + +/* Check presence of any devices on the bus */ +error_t UU_1WIRE_CheckPresence(Unit *unit, bool *presence) +{ + CHECK_TYPE(unit, &UNIT_1WIRE); + // reset + *presence = ow_reset(unit); + return E_SUCCESS; +} + +/* Read address of a lone device on the bus */ +error_t UU_1WIRE_ReadAddress(Unit *unit, uint64_t *address) +{ + CHECK_TYPE(unit, &UNIT_1WIRE); + *address = 0; + if (!ow_reset(unit)) return E_HW_TIMEOUT; + + // command + ow_write_u8(unit, OW_ROM_READ); + + // read the ROM code + *address = ow_read_u64(unit); + + const uint8_t *addr_as_bytes = (void*)address; + if (0 != ow_checksum(addr_as_bytes, 8)) { + *address = 0; + return E_CHECKSUM_MISMATCH; // checksum mismatch + } + return E_SUCCESS; +} + +/* Write bytes to a device */ +error_t UU_1WIRE_Write(Unit *unit, uint64_t address, const uint8_t *buff, uint32_t len) +{ + CHECK_TYPE(unit, &UNIT_1WIRE); + if (!ow_reset(unit)) return E_HW_TIMEOUT; + + // MATCH_ROM+addr, or SKIP_ROM + if (address != 0) { + ow_write_u8(unit, OW_ROM_MATCH); + ow_write_u64(unit, address); + } else { + ow_write_u8(unit, OW_ROM_SKIP); + } + + // write the payload; + for (uint32_t i = 0; i < len; i++) { + ow_write_u8(unit, *buff++); + } + return E_SUCCESS; +} + +/* Write a request to a device and read a response */ +error_t UU_1WIRE_Read(Unit *unit, uint64_t address, + const uint8_t *request_buff, uint32_t request_len, + uint8_t *response_buff, uint32_t response_len, bool check_crc) +{ + CHECK_TYPE(unit, &UNIT_1WIRE); + if (!ow_reset(unit)) return E_HW_TIMEOUT; + + uint8_t *rb = response_buff; + + // MATCH_ROM+addr, or SKIP_ROM + if (address != 0) { + ow_write_u8(unit, OW_ROM_MATCH); + ow_write_u64(unit, address); + } else { + ow_write_u8(unit, OW_ROM_SKIP); + } + + // write the payload; + for (uint32_t i = 0; i < request_len; i++) { + ow_write_u8(unit, *request_buff++); + } + + // read the requested number of bytes + for (uint32_t i = 0; i < response_len; i++) { + *rb++ = ow_read_u8(unit); + } + + if (check_crc) { + if (0 != ow_checksum(response_buff, response_len)) { + return E_CHECKSUM_MISMATCH; + } + } + return E_SUCCESS; +} + +/* Perform the search algorithm (start or continue) */ +error_t UU_1WIRE_Search(Unit *unit, bool with_alarm, bool restart, + uint64_t *buffer, uint32_t capacity, uint32_t *real_count, + bool *have_more) +{ + CHECK_TYPE(unit, &UNIT_1WIRE); + struct priv *priv = unit->data; + + if (restart) { + uint8_t search_cmd = (uint8_t) (with_alarm ? OW_ROM_ALM_SEARCH : OW_ROM_SEARCH); + ow_search_init(unit, search_cmd, true); + } + + *real_count = ow_search_run(unit, (ow_romcode_t *) buffer, capacity); + + // resolve the code + switch (priv->searchState.status) { + case OW_SEARCH_MORE: + *have_more = priv->searchState.status == OW_SEARCH_MORE; + + case OW_SEARCH_DONE: + return E_SUCCESS; + + case OW_SEARCH_FAILED: + return priv->searchState.error; + } + + return E_INTERNAL_ERROR; +} diff --git a/GexUnits/1wire/_ow_commands.h b/GexUnits/1wire/_ow_commands.h new file mode 100644 index 0000000..7c7b4a6 --- /dev/null +++ b/GexUnits/1wire/_ow_commands.h @@ -0,0 +1,18 @@ +// +// Created by MightyPork on 2018/01/29. +// + +#ifndef GEX_F072_OW_COMMANDS_H +#define GEX_F072_OW_COMMANDS_H + +#ifndef OW_INTERNAL +#error bad include! +#endif + +#define OW_ROM_SEARCH 0xF0 +#define OW_ROM_READ 0x33 +#define OW_ROM_MATCH 0x55 +#define OW_ROM_SKIP 0xCC +#define OW_ROM_ALM_SEARCH 0xEC + +#endif //GEX_F072_OW_COMMANDS_H diff --git a/GexUnits/1wire/_ow_init.c b/GexUnits/1wire/_ow_init.c new file mode 100644 index 0000000..91bf86d --- /dev/null +++ b/GexUnits/1wire/_ow_init.c @@ -0,0 +1,59 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define OW_INTERNAL +#include "_ow_internal.h" + +/** Allocate data structure and set defaults */ +error_t OW_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // some defaults + priv->pin_number = 0; + priv->port_name = 'A'; + priv->parasitic = false; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t OW_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // --- Parse config --- + priv->ll_pin = hw_pin2ll(priv->pin_number, &suc); + priv->port = hw_port2periph(priv->port_name, &suc); + Resource rsc = rsc_portpin2rsc(priv->port_name, priv->pin_number, &suc); + if (!suc) return E_BAD_CONFIG; + + // --- Claim resources --- + TRY(rsc_claim(unit, rsc)); + + // --- Init hardware --- + LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(priv->port, priv->ll_pin, LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinSpeed(priv->port, priv->ll_pin, LL_GPIO_SPEED_FREQ_HIGH); + LL_GPIO_SetPinPull(priv->port, priv->ll_pin, LL_GPIO_PULL_UP); // pull-up for OD state + + return E_SUCCESS; +} + +/** Tear down the unit */ +void OW_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // Release all resources + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/1wire/_ow_internal.h b/GexUnits/1wire/_ow_internal.h new file mode 100644 index 0000000..436dd93 --- /dev/null +++ b/GexUnits/1wire/_ow_internal.h @@ -0,0 +1,60 @@ +// +// Created by MightyPork on 2018/01/29. +// + +#ifndef GEX_F072_OW_INTERNAL_H +#define GEX_F072_OW_INTERNAL_H + +#ifndef OW_INTERNAL +#error bad include! +#endif + +#include "_ow_search.h" + +/** Private data structure */ +struct priv { + char port_name; + uint8_t pin_number; + bool parasitic; + + GPIO_TypeDef *port; + uint32_t ll_pin; + + TimerHandle_t busyWaitTimer; // timer used to wait for ds1820 measurement completion + bool busy; // flag used when the timer is running + uint32_t busyStart; + TF_ID busyRequestId; + struct ow_search_state searchState; +}; + +/** Load from a binary buffer stored in Flash */ +void OW_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void OW_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t OW_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void OW_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Allocate data structure and set defaults */ +error_t OW_preInit(Unit *unit); + +/** Finalize unit set-up */ +error_t OW_init(Unit *unit); + +/** Tear down the unit */ +void OW_deInit(Unit *unit); + +/** Callback for the FreeRTOS timer used to wait for device ready */ +void OW_TimerCb(TimerHandle_t xTimer); + +// ------------------------------------------------------------------------ + +#endif //GEX_F072_OW_INTERNAL_H diff --git a/GexUnits/1wire/_ow_low_level.c b/GexUnits/1wire/_ow_low_level.c new file mode 100644 index 0000000..35097d9 --- /dev/null +++ b/GexUnits/1wire/_ow_low_level.c @@ -0,0 +1,223 @@ +// +// Created by MightyPork on 2018/01/29. +// +// 1-Wire unit low level functions +// + +#include "platform.h" + +#define OW_INTERNAL +#include "_ow_internal.h" +#include "_ow_low_level.h" + +static inline uint8_t crc8_bits(uint8_t data) +{ + uint8_t crc = 0; + if(data & 1) crc ^= 0x5e; + if(data & 2) crc ^= 0xbc; + if(data & 4) crc ^= 0x61; + if(data & 8) crc ^= 0xc2; + if(data & 0x10) crc ^= 0x9d; + if(data & 0x20) crc ^= 0x23; + if(data & 0x40) crc ^= 0x46; + if(data & 0x80) crc ^= 0x8c; + return crc; +} + +static uint8_t crc8_add(uint8_t cksum, uint8_t byte) +{ + return crc8_bits(byte ^ cksum); +} + +uint8_t ow_checksum(const uint8_t *buff, uint32_t len) +{ + uint8_t cksum = 0; + for(uint32_t i = 0; i < len; i++) { + cksum = crc8_add(cksum, buff[i]); + } + return cksum; +} + +// ---------------------------------------------- + +static inline void ow_pull_high(Unit *unit) +{ + struct priv *priv = unit->data; + LL_GPIO_SetOutputPin(priv->port, priv->ll_pin); + LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_OUTPUT); +} + +static inline void ow_pull_low(Unit *unit) +{ + struct priv *priv = unit->data; + LL_GPIO_ResetOutputPin(priv->port, priv->ll_pin); + LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_OUTPUT); +} + +static inline void ow_release_line(Unit *unit) +{ + struct priv *priv = unit->data; + LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_INPUT); +} + +static inline bool ow_sample_line(Unit *unit) +{ + struct priv *priv = unit->data; + return (bool) LL_GPIO_IsInputPinSet(priv->port, priv->ll_pin); +} + +/** + * Reset the 1-wire bus + */ +bool ow_reset(Unit *unit) +{ + ow_pull_low(unit); + PTIM_MicroDelay(500); + + bool presence; + vPortEnterCritical(); + { + // Strong pull-up (for parasitive power) + ow_pull_high(unit); + PTIM_MicroDelay(2); + + // switch to open-drain + ow_release_line(unit); + PTIM_MicroDelay(118); + + presence = !ow_sample_line(unit); + } + vPortExitCritical(); + + PTIM_MicroDelay(130); + return presence; +} + +/** + * Write a bit to the 1-wire bus + */ +void ow_write_bit(Unit *unit, bool bit) +{ + vPortEnterCritical(); + { + // start mark + ow_pull_low(unit); + PTIM_MicroDelay(2); + + if (bit) ow_pull_high(unit); + PTIM_MicroDelay(70); + + // Strong pull-up (for parasitive power) + ow_pull_high(unit); + } + vPortExitCritical(); + + PTIM_MicroDelay(2); +} + +/** + * Read a bit from the 1-wire bus + */ +bool ow_read_bit(Unit *unit) +{ + bool bit; + + vPortEnterCritical(); + { + // start mark + ow_pull_low(unit); + PTIM_MicroDelay(2); + + ow_release_line(unit); + PTIM_MicroDelay(20); + + bit = ow_sample_line(unit); + } + vPortExitCritical(); + + PTIM_MicroDelay(40); + + return bit; +} + +/** + * Write a byte to the 1-wire bus + */ +void ow_write_u8(Unit *unit, uint8_t byte) +{ + for (int i = 0; i < 8; i++) { + ow_write_bit(unit, 0 != (byte & (1 << i))); + } +} + +/** + * Write a halfword to the 1-wire bus + */ +void ow_write_u16(Unit *unit, uint16_t halfword) +{ + ow_write_u8(unit, (uint8_t) (halfword & 0xFF)); + ow_write_u8(unit, (uint8_t) ((halfword >> 8) & 0xFF)); +} + +/** + * Write a word to the 1-wire bus + */ +void ow_write_u32(Unit *unit, uint32_t word) +{ + ow_write_u16(unit, (uint16_t) (word)); + ow_write_u16(unit, (uint16_t) (word >> 16)); +} + +/** + * Write a doubleword to the 1-wire bus + */ +void ow_write_u64(Unit *unit, uint64_t dword) +{ + ow_write_u32(unit, (uint32_t) (dword)); + ow_write_u32(unit, (uint32_t) (dword >> 32)); +} + +/** + * Read a byte form the 1-wire bus + */ +uint8_t ow_read_u8(Unit *unit) +{ + uint8_t buf = 0; + for (int i = 0; i < 8; i++) { + buf |= (1 & ow_read_bit(unit)) << i; + } + return buf; +} + +/** + * Read a halfword form the 1-wire bus + */ +uint16_t ow_read_u16(Unit *unit) +{ + uint16_t acu = 0; + acu |= ow_read_u8(unit); + acu |= ow_read_u8(unit) << 8; + return acu; +} + +/** + * Read a word form the 1-wire bus + */ +uint32_t ow_read_u32(Unit *unit) +{ + uint32_t acu = 0; + acu |= ow_read_u16(unit); + acu |= (uint32_t)ow_read_u16(unit) << 16; + return acu; +} + +/** + * Read a doubleword form the 1-wire bus + */ +uint64_t ow_read_u64(Unit *unit) +{ + uint64_t acu = 0; + acu |= ow_read_u32(unit); + acu |= (uint64_t)ow_read_u32(unit) << 32; + return acu; +} diff --git a/GexUnits/1wire/_ow_low_level.h b/GexUnits/1wire/_ow_low_level.h new file mode 100644 index 0000000..be22df4 --- /dev/null +++ b/GexUnits/1wire/_ow_low_level.h @@ -0,0 +1,84 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#ifndef GEX_F072_OW_LOW_LEVEL_H +#define GEX_F072_OW_LOW_LEVEL_H + +#ifndef OW_INTERNAL +#error bad include! +#endif + +#include "platform.h" +#include "unit_base.h" +#include "_ow_low_level.h" + +/** + * Compute a 1-wire type checksum. + * If the buffer includes the checksum, the result should be 0. + * + * (this function may be used externally, or you can delete the implementation + * from the c file if another implementation is already available) + * + * @param[in] buf - buffer of bytes to verify + * @param[in] len - buffer length + * @return checksum + */ +uint8_t ow_checksum(const uint8_t *buf, uint32_t len); + +/** + * Reset the 1-wire bus + */ +bool ow_reset(Unit *unit); + +/** + * Write a bit to the 1-wire bus + */ +void ow_write_bit(Unit *unit, bool bit); + +/** + * Read a bit from the 1-wire bus + */ +bool ow_read_bit(Unit *unit); + +/** + * Write a byte to the 1-wire bus + */ +void ow_write_u8(Unit *unit, uint8_t byte); + +/** + * Write a halfword to the 1-wire bus + */ +void ow_write_u16(Unit *unit, uint16_t halfword); + +/** + * Write a word to the 1-wire bus + */ +void ow_write_u32(Unit *unit, uint32_t word); + +/** + * Write a doubleword to the 1-wire bus + */ +void ow_write_u64(Unit *unit, uint64_t dword); + +/** + * Read a byte form the 1-wire bus + */ +uint8_t ow_read_u8(Unit *unit); + +/** + * Read a halfword form the 1-wire bus + */ +uint16_t ow_read_u16(Unit *unit); + +/** + * Read a word form the 1-wire bus + */ +uint32_t ow_read_u32(Unit *unit); + +/** + * Read a doubleword form the 1-wire bus + */ +uint64_t ow_read_u64(Unit *unit); + +#endif //GEX_F072_OW_LOW_LEVEL_H diff --git a/GexUnits/1wire/_ow_search.c b/GexUnits/1wire/_ow_search.c new file mode 100644 index 0000000..50cbe79 --- /dev/null +++ b/GexUnits/1wire/_ow_search.c @@ -0,0 +1,129 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#include "platform.h" +#include "unit_1wire.h" + +#define OW_INTERNAL +#include "_ow_search.h" +#include "_ow_internal.h" +#include "_ow_low_level.h" +#include "_ow_commands.h" + +void ow_search_init(Unit *unit, uint8_t command, bool test_checksums) +{ + if (unit->driver != &UNIT_1WIRE) + trap("Wrong unit type - %s", unit->driver->name); + + assert_param(command == OW_ROM_SEARCH || command == OW_ROM_ALM_SEARCH); + + struct priv *priv = unit->data; + struct ow_search_state *state = &priv->searchState; + + state->prev_last_fork = 64; + memset(state->prev_code, 0, 8); + state->status = OW_SEARCH_MORE; + state->error = E_SUCCESS; + state->command = command; + state->first = true; + state->test_checksums = test_checksums; +} + +uint32_t ow_search_run(Unit *unit, ow_romcode_t *codes, uint32_t capacity) +{ + if (unit->driver != &UNIT_1WIRE) + trap("Wrong unit type - %s", unit->driver->name); + + assert_param(codes); + + struct priv *priv = unit->data; + struct ow_search_state *state = &priv->searchState; + + if (state->status != OW_SEARCH_MORE) return 0; + + uint32_t found_devices = 0; + + while (found_devices < capacity) { + uint8_t index = 0; + ow_romcode_t code = {}; + int8_t last_fork = -1; + + // Start a new transaction. Devices respond to reset + if (!ow_reset(unit)) { + state->status = OW_SEARCH_FAILED; + state->error = E_HW_TIMEOUT; + goto done; + } + // Send the search command (SEARCH_ROM, SEARCH_ALARM) + ow_write_u8(unit, state->command); + + uint8_t *code_byte = &code[0]; + + bool p, n; + while (index != 64) { + // Read a bit and its complement + p = ow_read_bit(unit); + n = ow_read_bit(unit); + + if (!p && !n) { + // A fork: there are devices on the bus with different bit value + // (the bus is open-drain, in both cases one device pulls it low) + if ((found_devices > 0 || !state->first) && index < state->prev_last_fork) { + // earlier than the last fork, take the same turn as before + p = ow_code_getbit(state->prev_code, index); + if (!p) last_fork = index; // remember for future runs, 1 not explored yet + } + else if (index == state->prev_last_fork) { + p = 1; // both forks are now exhausted + } + else { // a new fork + last_fork = index; + } + } + else if (p && n) { + // No devices left connected - this doesn't normally happen + state->status = OW_SEARCH_FAILED; + state->error = E_BUS_FAULT; + goto done; + } + + // All devices have a matching bit here, or it was resolved in a fork + if (p) *code_byte |= (1 << (index & 7)); + ow_write_bit(unit, p); + + index++; + if((index & 7) == 0) { + code_byte++; + } + } + + memcpy(state->prev_code, code, 8); + + if (state->test_checksums) { + if (0 != ow_checksum(code, 8)) { + state->status = OW_SEARCH_FAILED; + state->error = E_CHECKSUM_MISMATCH; + goto done; + } + } + + // Record a found address + memcpy(codes[found_devices], code, 8); + found_devices++; + + // Stop condition + if (last_fork == -1) { + state->status = OW_SEARCH_DONE; + goto done; + } + + state->prev_last_fork = last_fork; + } + +done: + state->first = false; + return found_devices; +} + + diff --git a/GexUnits/1wire/_ow_search.h b/GexUnits/1wire/_ow_search.h new file mode 100644 index 0000000..738ef6d --- /dev/null +++ b/GexUnits/1wire/_ow_search.h @@ -0,0 +1,83 @@ +// +// Created by MightyPork on 2018/02/01. +// + +#ifndef GEX_F072_OW_SEARCH_H +#define GEX_F072_OW_SEARCH_H + +#ifndef OW_INTERNAL +#error bad include! +#endif + +#include +#include +#include "unit_base.h" + +// -------------------------------------------------------------------------------------- + +/** + * Data type holding a romcode + */ +typedef uint8_t ow_romcode_t[8]; + +/** + * Get a single bit from a romcode + */ +#define ow_code_getbit(code, index) (bool)((code)[(index) >> 3] & (1 << ((index) & 7))) + +/** + * Convert to unsigned 64-bit integer + * (works only on little-endian systems - eg. OK on x86/x86_64, not on PowerPC) + */ +#define ow_romcode_to_u64(code) (*((uint64_t *) (void *)(code))) + +/** + * States of the search algorithm + */ +enum ow_search_result { + OW_SEARCH_DONE = 0, + OW_SEARCH_MORE = 1, + OW_SEARCH_FAILED = 2, +}; + +/** + * Internal state of the search algorithm. + * Check status to see if more remain to be read or an error occurred. + */ +struct ow_search_state { + int8_t prev_last_fork; + ow_romcode_t prev_code; + uint8_t command; + enum ow_search_result status; + bool first; + bool test_checksums; + error_t error; +}; + +/** + * Init the search algorithm state structure + * + * @param[out] state - inited struct + * @param[in] command - command to send for requesting the search (e.g. SEARCH_ROM) + * @param[in] test_checksums - verify checksums of all read romcodes + */ +void ow_search_init(Unit *unit, uint8_t command, bool test_checksums); + +/** + * Perform a search of the 1-wire bus, with a state struct pre-inited + * using ow_search_init(). + * + * Romcodes are stored in the provided array in a numerically ascending order. + * + * This function may be called repeatedly to retrieve more addresses than could fit + * in the address buffer. + * + * @param[in,out] state - search state, used for multiple calls with limited buffer size + * @param[out] codes - buffer for found romcodes + * @param[in] capacity - buffer capacity + * @return number of romcodes found. Search status is stored in `state->status`, + * possible error code in `status->error` + */ +uint32_t ow_search_run(Unit *unit, ow_romcode_t *codes, uint32_t capacity); + +#endif //GEX_F072_OW_SEARCH_H diff --git a/GexUnits/1wire/_ow_settings.c b/GexUnits/1wire/_ow_settings.c new file mode 100644 index 0000000..c8daa4c --- /dev/null +++ b/GexUnits/1wire/_ow_settings.c @@ -0,0 +1,68 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define OW_INTERNAL +#include "_ow_internal.h" + +/** Load from a binary buffer stored in Flash */ +void OW_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->port_name = pp_char(pp); + priv->pin_number = pp_u8(pp); + priv->parasitic = pp_bool(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void OW_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_char(pb, priv->port_name); + pb_u8(pb, priv->pin_number); + pb_bool(pb, priv->parasitic); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t OW_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "pin")) { + suc = cfg_portpin_parse(value, &priv->port_name, &priv->pin_number); + } + else if (streq(key, "parasitic")) { + priv->parasitic = cfg_bool_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void OW_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Data pin"); + iw_entry(iw, "pin", "%c%d", priv->port_name, priv->pin_number); + + iw_comment(iw, "Parasitic (bus-powered) mode"); + iw_entry_s(iw, "parasitic", str_yn(priv->parasitic)); +} diff --git a/GexUnits/1wire/unit_1wire.c b/GexUnits/1wire/unit_1wire.c new file mode 100644 index 0000000..d74ed2c --- /dev/null +++ b/GexUnits/1wire/unit_1wire.c @@ -0,0 +1,246 @@ +// +// Created by MightyPork on 2018/01/29. +// + +#include "comm/messages.h" +#include "unit_base.h" +#include "unit_1wire.h" + +// 1WIRE master +#define OW_INTERNAL +#include "_ow_internal.h" +#include "_ow_low_level.h" + +/** Callback for sending a poll_ready success / failure report */ +static void OW_TimerRespCb(Job *job) +{ + Unit *unit = job->unit; + assert_param(unit); + struct priv *priv = unit->data; + + bool success = (bool) job->data1; + + if (success) { + com_respond_ok(priv->busyRequestId); + } else { + com_respond_error(priv->busyRequestId, E_HW_TIMEOUT); + } + priv->busy = false; +} + +/** + * 1-Wire timer callback, used for the 'wait_ready' function. + * + * - In parasitic mode, this is a simple 750ms wait, after which a SUCCESS response is sent. + * - In 3-wire mode, the callback is fired periodically and performs a Read operation on the bus. + * The unit responds with 0 while the operation is ongoing. On receiving 1 a SUCCESS response is sent. + * The polling is abandoned after a timeout, sending a TIMEOUT response. + * + * @param xTimer + */ +void OW_tickHandler(Unit *unit) +{ + struct priv *priv = unit->data; + if(!priv->busy) { + dbg("ow tick should be disabled now!"); + return; + } + + if (priv->parasitic) { + // this is the end of the 750ms measurement time + goto halt_ok; + } else { + bool ready = ow_read_bit(unit); + if (ready) { + goto halt_ok; + } + + uint32_t time = PTIM_GetTime(); + if (time - priv->busyStart > 1000) { + unit->tick_interval = 0; + unit->_tick_cnt = 0; + + Job j = { + .unit = unit, + .data1 = 0, // failure + .cb = OW_TimerRespCb, + }; + scheduleJob(&j); + } + } + + return; +halt_ok: + unit->tick_interval = 0; + unit->_tick_cnt = 0; + + Job j = { + .unit = unit, + .data1 = 1, // success + .cb = OW_TimerRespCb, + }; + scheduleJob(&j); +} + +enum PinCmd_ { + CMD_CHECK_PRESENCE = 0, // simply tests that any devices are attached + CMD_SEARCH_ADDR = 1, // perform a scan of the bus, retrieving all found device ROMs + CMD_SEARCH_ALARM = 2, // like normal scan, but retrieve only devices with alarm + CMD_SEARCH_CONTINUE = 3, // continue the previously started scan, retrieving more devices + CMD_READ_ADDR = 4, // read the ROM code from a single device (for single-device bus) + + CMD_WRITE = 10, // write multiple bytes using the SKIP_ROM command + CMD_READ = 11, // write multiple bytes using a ROM address + + CMD_POLL_FOR_1 = 20, +}; + + +/** Handle a request message */ +static error_t OW_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + bool presence; + uint64_t addr; + uint32_t remain; + const uint8_t *tail; + + if (priv->busy) return E_BUSY; + + bool with_alarm = false; + bool search_reset = false; + + switch (command) { + /** + * This is the delay function for DS1820 measurements. + * + * Parasitic: Returns success after the required 750ms + * Non-parasitic: Returns SUCCESS after device responds '1', HW_TIMEOUT after 1s + */ + case CMD_POLL_FOR_1: + // This can't be exposed via the UU API, due to being async + unit->_tick_cnt = 0; + unit->tick_interval = 750; + if (priv->parasitic) { + unit->tick_interval = 750; + } else { + unit->tick_interval = 10; + } + priv->busy = true; + priv->busyStart = PTIM_GetTime(); + priv->busyRequestId = frame_id; + return E_SUCCESS; // We will respond when the timer expires + + /** Search devices with alarm. No payload, restarts the search. */ + case CMD_SEARCH_ALARM: + with_alarm = true; + // fall-through + /** Search all devices. No payload, restarts the search. */ + case CMD_SEARCH_ADDR: + search_reset = true; + // fall-through + /** Continue a previously begun search. */ + case CMD_SEARCH_CONTINUE:; + uint32_t found_count = 0; + bool have_more = false; + if (!search_reset && priv->searchState.status != OW_SEARCH_MORE) { + dbg("Search not ongoing!"); + return E_PROTOCOL_BREACH; + } + + TRY(UU_1WIRE_Search(unit, with_alarm, search_reset, + (void *) unit_tmp512, UNIT_TMP_LEN/8, &found_count, + &have_more)); + + // use multipart to avoid allocating extra buffer + uint8_t status_code = (uint8_t) have_more; + TF_Msg msg = { + .frame_id = frame_id, + .type = MSG_SUCCESS, + .len = (TF_LEN) (found_count * 8 + 1), + }; + TF_Respond_Multipart(comm, &msg); + TF_Multipart_Payload(comm, &status_code, 1); + // the codes are back-to-back stored inside the buffer, we send it directly + // (it's already little-endian, as if built by PayloadBuilder) + TF_Multipart_Payload(comm, (uint8_t *) unit_tmp512, found_count * 8); + TF_Multipart_Close(comm); + return E_SUCCESS; + + /** Simply check presence of any devices on the bus. Responds with SUCCESS or HW_TIMEOUT */ + case CMD_CHECK_PRESENCE: + TRY(UU_1WIRE_CheckPresence(unit, &presence)); + + com_respond_u8(frame_id, (uint8_t) presence); + return E_SUCCESS; + + /** Read address of the single device on the bus - returns u64 */ + case CMD_READ_ADDR: + TRY(UU_1WIRE_ReadAddress(unit, &addr)); + + // build response + PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); + pb_u64(&pb, addr); + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Write payload to the bus, no confirmation (unless requested). + * + * Payload: + * addr:u64, rest:write_data + * if addr is 0, use SKIP_ROM + */ + case CMD_WRITE: + addr = pp_u64(pp); + tail = pp_tail(pp, &remain); + TRY(UU_1WIRE_Write(unit, addr, tail, remain)); + return E_SUCCESS; + + /** + * Write and read. + * + * Payload: + * addr:u64, read_len:u16, rest:write_data + * if addr is 0, use SKIP_ROM + */ + case CMD_READ: + addr = pp_u64(pp); + uint16_t rcount = pp_u16(pp); + bool test_crc = pp_bool(pp); + tail = pp_tail(pp, &remain); + + TRY(UU_1WIRE_Read(unit, addr, + tail, remain, + (uint8_t *) unit_tmp512, rcount, + test_crc)); + + // build response + com_respond_buf(frame_id, MSG_SUCCESS, (uint8_t *) unit_tmp512, rcount); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_1WIRE = { + .name = "1WIRE", + .description = "1-Wire master", + // Settings + .preInit = OW_preInit, + .cfgLoadBinary = OW_loadBinary, + .cfgWriteBinary = OW_writeBinary, + .cfgLoadIni = OW_loadIni, + .cfgWriteIni = OW_writeIni, + // Init + .init = OW_init, + .deInit = OW_deInit, + // Function + .handleRequest = OW_handleRequest, + .updateTick = OW_tickHandler, +}; diff --git a/GexUnits/1wire/unit_1wire.h b/GexUnits/1wire/unit_1wire.h new file mode 100644 index 0000000..767ed30 --- /dev/null +++ b/GexUnits/1wire/unit_1wire.h @@ -0,0 +1,79 @@ +// +// Created by MightyPork on 2018/01/02. +// +// Dallas 1-Wire master unit +// + +#ifndef GEX_F072_UNIT_1WIRE_H +#define GEX_F072_UNIT_1WIRE_H + +#include "unit.h" + +extern const UnitDriver UNIT_1WIRE; + +/** + * Check if there are any units present on the bus + * + * @param[in,out] unit + * @param[out] presence - any devices present + * @return success + */ +error_t UU_1WIRE_CheckPresence(Unit *unit, bool *presence); + +/** + * Read a device's address (use only with a single device attached) + * + * @param[in,out] unit + * @param[out] address - the device's address, 0 on error or CRC mismatch + * @return success + */ +error_t UU_1WIRE_ReadAddress(Unit *unit, uint64_t *address); + +/** + * Write bytes to a device / devices + * + * @param[in,out] unit + * @param[in] address - device address, 0 to skip match (single device or broadcast) + * @param[in] buff - bytes to write + * @param[in] len - buffer length + * @return success + */ +error_t UU_1WIRE_Write(Unit *unit, uint64_t address, const uint8_t *buff, uint32_t len); + +/** + * Read bytes from a device / devices, first writing a query + * + * @param[in,out] unit + * @param[in] address - device address, 0 to skip match (single device ONLY!) + * @param[in] request_buff - bytes to write before reading a response + * @param[in] request_len - number of bytes to write + * @param[out] response_buff - buffer for storing the read response + * @param[in] response_len - number of bytes to read + * @param[in] check_crc - verify CRC + * @return success + */ +error_t UU_1WIRE_Read(Unit *unit, uint64_t address, + const uint8_t *request_buff, uint32_t request_len, + uint8_t *response_buff, uint32_t response_len, bool check_crc); + +/** + * Perform a ROM search operation. + * The algorithm is on a depth-first search without backtracking, + * taking advantage of the open-drain topology. + * + * This function either starts the search, or continues it. + * + * @param[in,out] unit + * @param[in] with_alarm - true to match only devices in alarm state + * @param[in] restart - true to restart the search (search from the lowest address) + * @param[out] buffer - buffer for storing found addresses + * @param[in] capacity - buffer capacity in address entries (8 bytes) + * @param[out] real_count - real number of found addresses (for which the CRC matched) + * @param[out] have_more - flag indicating there are more devices to be found + * @return success + */ +error_t UU_1WIRE_Search(Unit *unit, bool with_alarm, bool restart, + uint64_t *buffer, uint32_t capacity, uint32_t *real_count, + bool *have_more); + +#endif //GEX_F072_UNIT_1WIRE_H diff --git a/GexUnits/adc/_adc_core.c b/GexUnits/adc/_adc_core.c new file mode 100644 index 0000000..26144f0 --- /dev/null +++ b/GexUnits/adc/_adc_core.c @@ -0,0 +1,700 @@ +// +// Created by MightyPork on 2018/02/04. +// +// The core functionality of the ADC unit is defined here. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_adc.h" + +#define ADC_INTERNAL +#include "_adc_internal.h" + +#define DMA_POS(priv) ((priv)->buf_itemcount - (priv)->DMA_CHx->CNDTR) + +/** + * Async job to send a chunk of the DMA buffer to PC. + * This can't be done directly because the interrupt couldn't wait for the TinyFrame mutex. + * + * unit - unit + * data1 - start index + * data2 - number of samples to send + * data3 - bit flags: 0x80 if this is the last sample and we should close + * 0x01 if this was the TC interrupt (otherwise it's HT) + */ +static void UADC_JobSendBlockChunk(Job *job) +{ + Unit *unit = job->unit; + struct priv *priv = unit->data; + + const uint32_t start = job->data1; + const uint32_t count = job->data2; + const bool close = (bool) (job->data3 & 0x80); + const bool tc = (bool) (job->data3 & 0x01); + + const TF_TYPE type = close ? EVT_CAPT_DONE : EVT_CAPT_MORE; + + TF_Msg msg = { + .frame_id = priv->stream_frame_id, + .len = (TF_LEN) (1 /*seq*/ + count * sizeof(uint16_t)), + .type = type, + }; + + assert_param(true == TF_Respond_Multipart(comm, &msg)); + TF_Multipart_Payload(comm, &priv->stream_serial, 1); + TF_Multipart_Payload(comm, (uint8_t *) (priv->dma_buffer + start), count * sizeof(uint16_t)); + TF_Multipart_Close(comm); + + // Clear the "busy" flags - those are checked in the DMA ISR to detect overrun + if (tc) priv->tc_pending = false; + else priv->ht_pending = false; + + priv->stream_serial++; +} + +/** + * Async job to send the trigger header. + * The header includes info about the trigger + the pre-trigger buffer. + * + * data1 - index in the DMA buffer at which the captured data willl start + * data2 - edge type - 1 rise, 2 fall, 3 forced + * timestamp - event stamp + * unit - unit + */ +static void UADC_JobSendTriggerCaptureHeader(Job *job) +{ + Unit *unit = job->unit; + struct priv *priv = unit->data; + + EventReport er = { + .unit = unit, + .type = EVT_CAPT_START, + .timestamp = job->timestamp, + .length = (priv->pretrig_len + ((priv->pretrig_len > 0)?1:0)) * // see below why +1 + priv->nb_channels * + sizeof(uint16_t) + + 4 /*pretrig len*/ + + 1 /*edge*/ + + 1 /* seq */ + }; + + uint32_t index_trigd = job->data1; + uint8_t edge = (uint8_t) job->data2; + + EventReport_Start(&er); + priv->stream_frame_id = er.sent_msg_id; + { + // preamble + uint8_t buf[6]; + PayloadBuilder pb = pb_start(buf, 6, NULL); + pb_u32(&pb, priv->pretrig_len); + pb_u8(&pb, edge); + pb_u8(&pb, priv->stream_serial++); // This is the serial counter for the first chunk + // (containing the pre-trigger, or empty if no pretrig configured) + EventReport_PB(&pb); + + if (priv->pretrig_len > 0) { + // pretrig + + // +1 because we want pretrig 0 to exactly start with the triggering sample + uint32_t pretrig_remain = (priv->pretrig_len + 1) * priv->nb_channels; + + assert_param(index_trigd <= priv->buf_itemcount); + + // this is one past the last entry of the triggering capture group + if (pretrig_remain > index_trigd) { + // used items in the wrap-around part of the buffer + uint32_t items_from_end = pretrig_remain - index_trigd; + assert_param(priv->buf_itemcount - items_from_end >= index_trigd); + + EventReport_Data((uint8_t *) &priv->dma_buffer[priv->buf_itemcount - items_from_end], + items_from_end * sizeof(uint16_t)); + + assert_param(items_from_end <= pretrig_remain); + pretrig_remain -= items_from_end; + } + + assert_param(pretrig_remain <= index_trigd); + EventReport_Data((uint8_t *) &priv->dma_buffer[index_trigd - pretrig_remain], + pretrig_remain * sizeof(uint16_t)); + } + } + EventReport_End(); +} + +/** + * Async job to notify about end of stream + */ +static void UADC_JobSendEndOfStreamMsg(Job *job) +{ + TF_Msg msg = { + .type = EVT_CAPT_DONE, + .frame_id = (TF_ID) job->data1 + }; + TF_Respond(comm, &msg); +} + +/** + * Schedule sending a event report to the PC that the current stream has ended. + * The client library should handle this appropriately. + */ +void UADC_ReportEndOfStream(Unit *unit) +{ + struct priv *priv = unit->data; + + Job j = { + .unit = unit, + .data1 = priv->stream_frame_id, // copy the ID, it may be invalid by the time the cb gets executed + .cb = UADC_JobSendEndOfStreamMsg + }; + scheduleJob(&j); +} + +/** + * This is a helper function for the ADC DMA interrupt for handing the different interrupt types (half / full transfer). + * It sends the part of the buffer that was just captured via an async job, or aborts on overrun. + * + * It's split off here to allow calling it for the different flags without repeating code. + * + * @param unit + * @param tc - true if this is the TC interrupt, else HT + */ +static void handle_httc(Unit *unit, bool tc) +{ + struct priv *priv = unit->data; + uint32_t start = priv->stream_startpos; + uint32_t end; + const bool ht = !tc; + + const bool m_trigd = priv->opmode == ADC_OPMODE_TRIGD; + const bool m_stream = priv->opmode == ADC_OPMODE_STREAM; + const bool m_fixcpt = priv->opmode == ADC_OPMODE_BLCAP; + + if (ht) { + end = (priv->buf_itemcount >> 1); // div2 + } + else { + end = priv->buf_itemcount; + } + + if (start != end) { // this sometimes happened after a trigger, may be unnecessary now + if (end < start) { + // this was a trap for a bug with missed TC irq, it's hopefully fixed now + trap("end < start! %d < %d, tc %d", (int)end, (int)start, (int)tc); + } + + uint32_t sgcount = (end - start) / priv->nb_channels; + + if (m_trigd || m_fixcpt) { + sgcount = MIN(priv->trig_stream_remain, sgcount); + priv->trig_stream_remain -= sgcount; + } + + // Check for the closing condition + const bool close = !m_stream && priv->trig_stream_remain == 0; + + if ((tc && priv->tc_pending) || (ht && priv->ht_pending)) { + dbg("(!) ADC DMA not handled in time, abort capture"); + UADC_SwitchMode(unit, ADC_OPMODE_EMERGENCY_SHUTDOWN); + return; + } + + // Here we set the tc/ht pending flags for detecting overrun + + Job j = { + .unit = unit, + .data1 = start, + .data2 = sgcount * priv->nb_channels, + .data3 = (uint32_t) (close*0x80) | (tc*1), // bitfields to indicate what's happening + .cb = UADC_JobSendBlockChunk + }; + + if (tc) + priv->tc_pending = true; + else + priv->ht_pending = true; + + if (!scheduleJob(&j)) { + // Abort if we can't queue - the stream would tear and we'd hog the system with error messages + UADC_SwitchMode(unit, ADC_OPMODE_EMERGENCY_SHUTDOWN); + return; + } + + if (close) { + // If auto-arm is enabled, we need to re-arm again. + // However, EOS irq is disabled during the capture so the trigger edge detection would + // work on stale data from before this trigger. We have to wait for the next full + // conversion (EOS) before arming. + UADC_SwitchMode(unit, (priv->auto_rearm && m_trigd) ? ADC_OPMODE_REARM_PENDING : ADC_OPMODE_IDLE); + } + } + + // Advance the starting position + if (tc) { + priv->stream_startpos = 0; + } + else { + priv->stream_startpos = priv->buf_itemcount >> 1; // div2 + } +} + +/** + * IRQ handler for the DMA flags. + * + * We handle flags: + * TC - transfer complete + * HT - half transfer + * TE - transfer error (this should never happen unless there's a bug) + * + * The buffer works in a circular mode, so we always handle the previous half + * or what of it should be sent (if capture started somewhere inside). + * + * @param arg - the unit, passed via the irq dispatcher + */ +void UADC_DMA_Handler(void *arg) +{ + Unit *unit = arg; + struct priv *priv = unit->data; + + // First thing, grab the flags. They may change during the function. + // Working on the live register might cause race conditions. + const uint32_t isrsnapshot = priv->DMAx->ISR; + + if (priv->opmode == ADC_OPMODE_UNINIT) { + // the IRQ occured while switching mode, clear flags and do nothing else + LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum); + return; + } + + if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_chnum)) { + // we have some flags set - check which + const bool tc = LL_DMA_IsActiveFlag_TC(isrsnapshot, priv->dma_chnum); + const bool ht = LL_DMA_IsActiveFlag_HT(isrsnapshot, priv->dma_chnum); + const bool te = LL_DMA_IsActiveFlag_TE(isrsnapshot, priv->dma_chnum); + + if (ht) LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + if (tc) LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + + if (te) { + // this shouldn't happen - error + adc_dbg("ADC DMA TE!"); + LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum); + return; + } + + // check what mode we're in + const bool m_trigd = priv->opmode == ADC_OPMODE_TRIGD; + const bool m_stream = priv->opmode == ADC_OPMODE_STREAM; + const bool m_fixcpt = priv->opmode == ADC_OPMODE_BLCAP; + if (m_trigd || m_stream || m_fixcpt) { + const uint32_t half = (uint32_t) (priv->buf_itemcount >> 1); // div2 + if (ht && tc) { + // dual event interrupt - may happen if we missed both and they were pending after + // interrupts became enabled again (this can happen due to the EOS or other higher prio irq's) + + if (priv->stream_startpos > half) { + handle_httc(unit, true); // TC + handle_httc(unit, false); // HT + } else { + handle_httc(unit, false); // HT + handle_httc(unit, true); // TC + } + } else { + if (ht && priv->stream_startpos > half) { + // We missed the TC interrupt while e.g. setting up the stream / interrupt. catch up! + // This fixes a bug with "negative size" for report. + handle_httc(unit, true); // TC + } + + handle_httc(unit, tc); + } + } else { + // This shouldn't happen, the interrupt should be disabled in this opmode + dbg("(!) not streaming, ADC DMA IT should be disabled"); + } + } +} + +/** + * End of measurement group interrupt handler. + * This interrupt records the measured values and checks for trigger. + * + * @param arg - unit, passed b y irq dispatcher + */ +void UADC_ADC_EOS_Handler(void *arg) +{ + Unit *unit = arg; + struct priv *priv = unit->data; + + const bool can_average = priv->cfg.enable_averaging && + priv->real_frequency_int < UADC_MAX_FREQ_FOR_AVERAGING; + + // Normally + uint64_t timestamp = 0; + if (priv->opmode == ADC_OPMODE_ARMED) { + timestamp = PTIM_GetMicrotime(); + } + + LL_ADC_ClearFlag_EOS(priv->ADCx); + + if (priv->opmode == ADC_OPMODE_UNINIT) { + goto exit; + } + + uint32_t dmapos = DMA_POS(priv); + + // Wait for the DMA to complete copying the last sample + // XXX + // experiments revealed this was actually a bug somewhere else and DMA + // is quick enough so we don't have to worry about this +#if 0 + uint32_t err = (dmapos % priv->nb_channels); + if (err != 0) { + GPIOC->BSRR = 0x02; + hw_wait_while(((dmapos = DMA_POS(priv)) % priv->nb_channels) != 0, 10); + GPIOC->BRR = 0x02; + } +#endif + + // wrap dmapos to be past the last sample, even if outside the buffer + // - so we can subtract nb_channels + uint32_t sample_pos; + if (dmapos == 0) { + sample_pos = (uint32_t) (priv->buf_itemcount); + } else { + sample_pos = dmapos; + } + sample_pos -= priv->nb_channels; + + uint16_t val; + +#if 1 + for (uint32_t j = 0; j < priv->nb_channels; j++) { + const uint8_t i = priv->channel_nums[j]; + val = priv->dma_buffer[sample_pos+j]; + + if (can_average) { + priv->averaging_bins[i] = + priv->averaging_bins[i] * (1.0f - priv->avg_factor_as_float) + + ((float) val) * priv->avg_factor_as_float; + } + + priv->last_samples[i] = val; + } +#else + for (uint8_t i = 0; i < 18; i++) { + if (channels_mask & (1 << i)) { + val = priv->dma_buffer[sample_pos+cnt]; + + cnt++; + + if (can_average) { + priv->averaging_bins[i] = + priv->averaging_bins[i] * (1.0f - priv->avg_factor_as_float) + + ((float) val) * priv->avg_factor_as_float; + } + + priv->last_samples[i] = val; + } + } +#endif + + switch (priv->opmode) { + // Triggering condition test + case ADC_OPMODE_ARMED: + val = priv->last_samples[priv->trigger_source]; + + if ((priv->trig_prev_level < priv->trig_level) && + val >= priv->trig_level && + (bool) (priv->trig_edge & 0b01)) { + // Rising edge + UADC_HandleTrigger(unit, 0b01, timestamp); + } + else if ((priv->trig_prev_level > priv->trig_level) && + val <= priv->trig_level && + (bool) (priv->trig_edge & 0b10)) { + // Falling edge + UADC_HandleTrigger(unit, 0b10, timestamp); + } + priv->trig_prev_level = val; + break; + + // auto-rearm was waiting for the next sample + case ADC_OPMODE_REARM_PENDING: + if (!priv->auto_rearm) { + // It looks like the flag was cleared by DISARM before we got a new sample. + // Let's just switch to IDLE + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + } else { + // Re-arming for a new trigger + UADC_SwitchMode(unit, ADC_OPMODE_ARMED); + } + + default: + break; + } + +exit: + return; +} + +/** + * Handle a detected trigger - start capture if we're not in hold-off + * + * @param unit + * @param edge_type - edge type, is included in the report + * @param timestamp - event time + */ +void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp) +{ + struct priv *priv = unit->data; + if (priv->opmode == ADC_OPMODE_UNINIT) return; + + if (priv->trig_holdoff != 0 && priv->trig_holdoff_remain > 0) { + // Trig discarded due to holdoff + return; + } + + if (priv->trig_holdoff > 0) { + priv->trig_holdoff_remain = priv->trig_holdoff; + // Start the tick + unit->tick_interval = 1; + unit->_tick_cnt = 1; + } + + priv->stream_startpos = DMA_POS(priv); + priv->trig_stream_remain = priv->trig_len; + priv->stream_serial = 0; + + // This func may be called from the EOS interrupt, so it's safer to send the header message asynchronously + Job j = { + .unit = unit, + .timestamp = timestamp, + .data1 = priv->stream_startpos, + .data2 = edge_type, + .cb = UADC_JobSendTriggerCaptureHeader + }; + scheduleJob(&j); + + UADC_SwitchMode(unit, ADC_OPMODE_TRIGD); +} + +/** + * Abort ongoing capture. + */ +void UADC_AbortCapture(Unit *unit) +{ + struct priv *priv = unit->data; + + const enum uadc_opmode old_opmode = priv->opmode; + + priv->auto_rearm = false; + + if (old_opmode == ADC_OPMODE_BLCAP || + old_opmode == ADC_OPMODE_STREAM || + old_opmode == ADC_OPMODE_TRIGD) { + UADC_ReportEndOfStream(unit); + } + + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); +} + +/** + * Start a manual block capture. + * + * @param unit + * @param len - number of samples (groups) + * @param frame_id - TF session to re-use for the report (client has a listener set up) + */ +void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id) +{ + struct priv *priv = unit->data; + if (priv->opmode == ADC_OPMODE_UNINIT) return; + + priv->stream_frame_id = frame_id; + priv->stream_startpos = DMA_POS(priv); + priv->trig_stream_remain = len; + priv->stream_serial = 0; + UADC_SwitchMode(unit, ADC_OPMODE_BLCAP); +} + +/** + * Start a stream + * + * @param frame_id - TF session to re-use for the frames (client has a listener set up) + */ +void UADC_StartStream(Unit *unit, TF_ID frame_id) +{ + struct priv *priv = unit->data; + if (priv->opmode == ADC_OPMODE_UNINIT) return; + + priv->stream_frame_id = frame_id; + UADC_SwitchMode(unit, ADC_OPMODE_STREAM); +} + +/** + * End a stream by user request. + */ +void UADC_StopStream(Unit *unit) +{ + struct priv *priv = unit->data; + if (priv->opmode == ADC_OPMODE_UNINIT) return; + + UADC_ReportEndOfStream(unit); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); +} + +/** + * Handle unit update tick - expire the trigger hold-off. + * We also check for the emergency shutdown condition and clear it. + */ +void UADC_updateTick(Unit *unit) +{ + struct priv *priv = unit->data; + + // Recover from shutdown after a delay + if (priv->opmode == ADC_OPMODE_EMERGENCY_SHUTDOWN) { + adc_dbg("ADC recovering from emergency shutdown"); + + UADC_ReportEndOfStream(unit); + LL_TIM_EnableCounter(priv->TIMx); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + unit->tick_interval = 0; + return; + } + + if (priv->trig_holdoff_remain > 0) { + priv->trig_holdoff_remain--; + + if (priv->trig_holdoff_remain == 0) { + unit->tick_interval = 0; + unit->_tick_cnt = 0; + } + } +} + +/** + * Switch the ADC operational mode. + * + * @param unit + * @param new_mode - mode to set + */ +void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) +{ + struct priv *priv = unit->data; + + const enum uadc_opmode old_mode = priv->opmode; + if (new_mode == old_mode) return; // nothing to do + + // if un-itied, can go only to IDLE + assert_param((old_mode != ADC_OPMODE_UNINIT) || (new_mode == ADC_OPMODE_IDLE)); + + priv->opmode = ADC_OPMODE_UNINIT; + + if (new_mode == ADC_OPMODE_UNINIT) { + adc_dbg("ADC switch -> UNINIT"); + // Stop the DMA, timer and disable ADC - this is called before tearing down the unit + LL_TIM_DisableCounter(priv->TIMx); + LL_ADC_ClearFlag_EOS(priv->ADCx); + LL_ADC_DisableIT_EOS(priv->ADCx); + + // Switch off the ADC + if (LL_ADC_IsEnabled(priv->ADCx)) { + // Cancel ongoing conversion + if (LL_ADC_REG_IsConversionOngoing(priv->ADCx)) { + LL_ADC_REG_StopConversion(priv->ADCx); + hw_wait_while(LL_ADC_REG_IsStopConversionOngoing(priv->ADCx), 100); + } + + LL_ADC_Disable(priv->ADCx); + hw_wait_while(LL_ADC_IsDisableOngoing(priv->ADCx), 100); + } + + LL_DMA_DisableChannel(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); + LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + } + else if (new_mode == ADC_OPMODE_IDLE || new_mode == ADC_OPMODE_REARM_PENDING) { + // IDLE and ARMED are identical with the exception that the trigger condition is not checked + // ARMED can be only entered from IDLE, thus we do the init only here. + + priv->tc_pending = false; + priv->ht_pending = false; + + // In IDLE, we don't need the DMA interrupts + LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); + + // Use End Of Sequence to recover results for averaging from the DMA buffer and DR + LL_ADC_ClearFlag_EOS(priv->ADCx); + LL_ADC_EnableIT_EOS(priv->ADCx); + + if (old_mode == ADC_OPMODE_UNINIT) { + // Nothing is started yet - this is the only way to leave UNINIT + LL_ADC_Enable(priv->ADCx); + LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum); + LL_TIM_EnableCounter(priv->TIMx); + + LL_ADC_REG_StartConversion(priv->ADCx); + } + } + else if (new_mode == ADC_OPMODE_EMERGENCY_SHUTDOWN) { + adc_dbg("ADC switch -> EMERGENCY_STOP"); + // Emergency shutdown is used when the job queue overflows and the stream is torn + // This however doesn't help in the case when user sets such a high frequency + // that the whole app becomes unresponsive due to the completion ISR, need to verify the value manually. + + LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); + + LL_TIM_DisableCounter(priv->TIMx); + UADC_SetSampleRate(unit, 10000); // fallback to a known safe value + + LL_ADC_ClearFlag_EOS(priv->ADCx); + LL_ADC_DisableIT_EOS(priv->ADCx); + + unit->tick_interval = 0; + unit->_tick_cnt = 250; // 1-off + } + else if (new_mode == ADC_OPMODE_ARMED) { + adc_dbg("ADC switch -> ARMED"); + assert_param(old_mode == ADC_OPMODE_IDLE || old_mode == ADC_OPMODE_REARM_PENDING); + + // avoid firing immediately by the value jumping across the scale + priv->trig_prev_level = priv->last_samples[priv->trigger_source]; + } + else if (new_mode == ADC_OPMODE_TRIGD || new_mode == ADC_OPMODE_STREAM || new_mode == ADC_OPMODE_BLCAP) { + adc_dbg("ADC switch -> CAPTURE"); + + assert_param(old_mode == ADC_OPMODE_ARMED || old_mode == ADC_OPMODE_IDLE); + + // during the capture, we disallow direct readout and averaging to reduce overhead + LL_ADC_DisableIT_EOS(priv->ADCx); + + // Enable the DMA buffer interrupts + + // we must first clear the flags, otherwise it will cause WEIRD bugs in the handler + LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); + + // those must be as close as possible to the enabling + // if not trig'd, we don't care for lost samples before (this could cause a DMA irq miss / ht/tc mismatch with the startpos) + if (new_mode != ADC_OPMODE_TRIGD) { + priv->stream_startpos = DMA_POS(priv); + priv->stream_serial = 0; + } + LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum); + LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum); + } + + priv->opmode = new_mode; +} diff --git a/GexUnits/adc/_adc_init.c b/GexUnits/adc/_adc_init.c new file mode 100644 index 0000000..a314041 --- /dev/null +++ b/GexUnits/adc/_adc_init.c @@ -0,0 +1,269 @@ +// +// Created by MightyPork on 2018/02/03. +// +// ADC unit init and de-init functions +// + +#include "platform.h" +#include "unit_base.h" + +#define ADC_INTERNAL +#include "_adc_internal.h" + +/** Allocate data structure and set defaults */ +error_t UADC_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + priv->cfg.channels = 1<<16; // Tsense by default - always available, easy testing + priv->cfg.sample_time = 0b010; // 13.5c - good enough and the default 0b00 value really is useless + priv->cfg.frequency = 1000; + priv->cfg.buffer_size = 256; // in half-words + priv->cfg.averaging_factor = 500; // 0.5 + priv->cfg.enable_averaging = true; + + priv->opmode = ADC_OPMODE_UNINIT; + + return E_SUCCESS; +} + + +/** Configure frequency */ +error_t UADC_SetSampleRate(Unit *unit, uint32_t hertz) +{ + struct priv *priv = unit->data; + + uint16_t presc; + uint32_t count; + if (!hw_solve_timer(PLAT_APB1_HZ, hertz, true, &presc, &count, &priv->real_frequency)) { + dbg("Failed to resolve timer params."); + return E_BAD_VALUE; + } + adc_dbg("Frequency error %d ppm, presc %d, count %d", + (int) lrintf(1000000.0f * ((priv->real_frequency - hertz) / (float) hertz)), + (int) presc, (int) count); + + LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); + LL_TIM_SetAutoReload(priv->TIMx, count - 1); + + priv->real_frequency_int = hertz; + + return E_SUCCESS; +} + +/** + * Set up the ADC DMA. + * This is split to its own function because it's also called when the user adjusts the + * enabled channels and we need to re-configure it. + * + * @param unit + */ +void UADC_SetupDMA(Unit *unit) +{ + struct priv *priv = unit->data; + + adc_dbg("Setting up DMA"); + { + uint32_t itemcount = priv->nb_channels * (priv->cfg.buffer_size / (priv->nb_channels)); + if (itemcount % 2 == 1) itemcount -= priv->nb_channels; // ensure the count is even + priv->buf_itemcount = itemcount; + + adc_dbg("DMA item count is %d (%d bytes), There are %d samples per group.", + (int)priv->buf_itemcount, + (int)(priv->buf_itemcount * sizeof(uint16_t)), + (int)priv->nb_channels); + + { + LL_DMA_InitTypeDef init; + LL_DMA_StructInit(&init); + init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + + init.Mode = LL_DMA_MODE_CIRCULAR; + init.NbData = itemcount; + + init.PeriphOrM2MSrcAddress = (uint32_t) &priv->ADCx->DR; + init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; + init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + + init.MemoryOrM2MDstAddress = (uint32_t) priv->dma_buffer; + init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD; + init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + + assert_param(SUCCESS == LL_DMA_Init(priv->DMAx, priv->dma_chnum, &init)); + } + +// LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum); // this is done in the switch mode func now + } +} + +/** Finalize unit set-up */ +error_t UADC_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // Written for F072 which has only one ADC + + TRY(rsc_claim(unit, R_ADC1)); + TRY(rsc_claim(unit, R_DMA1_1)); + TRY(rsc_claim(unit, R_TIM15)); + + priv->DMAx = DMA1; + priv->DMA_CHx = DMA1_Channel1; + priv->dma_chnum = 1; + priv->ADCx = ADC1; + priv->ADCx_Common = ADC1_COMMON; + priv->TIMx = TIM15; + + // ----------------------- CONFIGURE PINS -------------------------- + { + // Claim and configure all analog pins + priv->nb_channels = 0; + for (uint8_t i = 0; i <= UADC_MAX_CHANNEL; i++) { + if (priv->cfg.channels & (1UL << i)) { + priv->channel_nums[priv->nb_channels] = (uint8_t) i; + priv->nb_channels++; + + do { + char c; + uint8_t num; + if (i <= 7) { + c = 'A'; + num = i; + } + else if (i <= 9) { + c = 'B'; + num = (uint8_t) (i - 8); + } + else if (i <= 15) { + c = 'C'; + num = (uint8_t) (i - 10); + } + else { + break; + } + + TRY(rsc_claim_pin(unit, c, num)); + uint32_t ll_pin = hw_pin2ll(num, &suc); + GPIO_TypeDef *port = hw_port2periph(c, &suc); + assert_param(suc); + + LL_GPIO_SetPinPull(port, ll_pin, LL_GPIO_PULL_NO); + LL_GPIO_SetPinMode(port, ll_pin, LL_GPIO_MODE_ANALOG); + } while (0); + } + } + + if (priv->nb_channels == 0) { + dbg("Need at least 1 channel"); + return E_BAD_CONFIG; + } + + // ensure some minimal space is available + if (priv->cfg.buffer_size < priv->nb_channels * 2) { + dbg("Insufficient buf size"); + return E_BAD_CONFIG; + } + } + + // ---------------- Alloc the buffer ---------------------- + adc_dbg("Allocating buffer of size %d half-words", (int)priv->cfg.buffer_size); + priv->dma_buffer = calloc_ck(priv->cfg.buffer_size, sizeof(uint16_t)); + if (NULL == priv->dma_buffer) return E_OUT_OF_MEM; + assert_param(((uint32_t) priv->dma_buffer & 3) == 0); // must be aligned + + // ------------------- ENABLE CLOCKS -------------------------- + { + // enable peripherals clock + hw_periph_clock_enable(priv->ADCx); + hw_periph_clock_enable(priv->TIMx); + // DMA and GPIO clocks are enabled on startup automatically + } + + // ------------------- CONFIGURE THE TIMER -------------------------- + adc_dbg("Setting up TIMER"); + { + TRY(UADC_SetSampleRate(unit, priv->cfg.frequency)); + + LL_TIM_EnableARRPreload(priv->TIMx); + LL_TIM_EnableUpdateEvent(priv->TIMx); + LL_TIM_SetTriggerOutput(priv->TIMx, LL_TIM_TRGO_UPDATE); + LL_TIM_GenerateEvent_UPDATE(priv->TIMx); // load the prescaller value + } + + // --------------------- CONFIGURE THE ADC --------------------------- + adc_dbg("Setting up ADC"); + { + // Calibrate the ADC + adc_dbg("Wait for calib"); + LL_ADC_StartCalibration(priv->ADCx); + while (LL_ADC_IsCalibrationOnGoing(priv->ADCx)) {} + adc_dbg("ADC calibrated."); + + // Let's just enable the internal channels always - makes toggling them on-line easier + LL_ADC_SetCommonPathInternalCh(priv->ADCx_Common, LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR); + + LL_ADC_SetDataAlignment(priv->ADCx, LL_ADC_DATA_ALIGN_RIGHT); + LL_ADC_SetResolution(priv->ADCx, LL_ADC_RESOLUTION_12B); + LL_ADC_REG_SetDMATransfer(priv->ADCx, LL_ADC_REG_DMA_TRANSFER_UNLIMITED); + + // configure channels + priv->channels_mask = priv->cfg.channels; + + priv->ADCx->CHSELR = priv->channels_mask; + + LL_ADC_REG_SetTriggerSource(priv->ADCx, LL_ADC_REG_TRIG_EXT_TIM15_TRGO); + + LL_ADC_SetSamplingTimeCommonChannels(priv->ADCx, LL_ADC_SAMPLETIMES[priv->cfg.sample_time]); + + // will be enabled when switching to INIT mode + } + + // --------------------- CONFIGURE DMA ------------------------------- + UADC_SetupDMA(unit); + + // prepare the avg factor float for the ISR + if (priv->cfg.averaging_factor > 1000) priv->cfg.averaging_factor = 1000; // normalize + priv->avg_factor_as_float = priv->cfg.averaging_factor/1000.0f; + + adc_dbg("ADC peripherals configured."); + + irqd_attach(priv->DMA_CHx, UADC_DMA_Handler, unit); + irqd_attach(priv->ADCx, UADC_ADC_EOS_Handler, unit); + adc_dbg("irqs attached"); + + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + adc_dbg("ADC done"); + + return E_SUCCESS; +} + +/** Tear down the unit */ +void UADC_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS ) { + UADC_SwitchMode(unit, ADC_OPMODE_UNINIT); + + //LL_ADC_DeInit(priv->ADCx); + LL_ADC_CommonDeInit(priv->ADCx_Common); + LL_TIM_DeInit(priv->TIMx); + + irqd_detach(priv->DMA_CHx, UADC_DMA_Handler); + irqd_detach(priv->ADCx, UADC_ADC_EOS_Handler); + + LL_DMA_DeInit(priv->DMAx, priv->dma_chnum); + } + + // free buffer if not NULL + free_ck(priv->dma_buffer); + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/adc/_adc_internal.h b/GexUnits/adc/_adc_internal.h new file mode 100644 index 0000000..c356f23 --- /dev/null +++ b/GexUnits/adc/_adc_internal.h @@ -0,0 +1,161 @@ +// +// Created by MightyPork on 2018/02/03. +// +// Defines and prototypes used internally by the ADC unit. +// + +#ifndef GEX_F072_ADC_INTERNAL_H +#define GEX_F072_ADC_INTERNAL_H + +#ifndef ADC_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +//#define adc_dbg dbg +#define adc_dbg(...) do {} while(0) + +#define UADC_MAX_FREQ_FOR_AVERAGING 20000 + +#define UADC_MAX_CHANNEL 17 + +enum uadc_opmode { + ADC_OPMODE_UNINIT, //!< Not yet switched to any mode + ADC_OPMODE_IDLE, //!< Idle. Allows immediate value readout and averaging. + ADC_OPMODE_REARM_PENDING, //!< Idle, waiting for the next sample to re-arm (auto trigger). + ADC_OPMODE_ARMED, //!< Armed for a trigger. Direct access and averaging are disabled. + ADC_OPMODE_TRIGD, //!< Triggered, sending pre-trigger and streaming captured data. + ADC_OPMODE_BLCAP, //!< Capture of fixed length without a trigger + ADC_OPMODE_STREAM, //!< Unlimited capture + ADC_OPMODE_EMERGENCY_SHUTDOWN, //!< Used when the buffers overrun to safely transition to IDLE after a delay +}; + +enum uadc_event { + EVT_CAPT_START = 50, //!< Capture start (used in event in the first frame when trigger is detected) + EVT_CAPT_MORE = 51, //!< Capture data payload (used as TYPE for all capture types) + EVT_CAPT_DONE = 52, //!< End of trig'd or block capture payload (last frame with data), + //!< or a farewell message after closing stream using abort(), in this case without data. +}; + +/** Private data structure */ +struct priv { + struct { + uint32_t channels; //!< bit flags (will be recorded in order 0-15) + uint8_t sample_time; //!< 0-7 (corresponds to 1.5-239.5 cycles) - time for the sampling capacitor to charge + uint32_t frequency; //!< Timer frequency in Hz. Note: not all frequencies can be achieved accurately + uint32_t buffer_size; //!< Buffer size in bytes (count 2 bytes per channel per measurement) - faster sampling freq needs bigger buffer + uint16_t averaging_factor; //!< Exponential averaging factor 0-1000 + bool enable_averaging; + } cfg; + + // Peripherals + ADC_TypeDef *ADCx; //!< The ADC peripheral used + ADC_Common_TypeDef *ADCx_Common; //!< The ADC common control block + TIM_TypeDef *TIMx; //!< ADC timing timer instance + DMA_TypeDef *DMAx; //!< DMA isnatnce used + uint8_t dma_chnum; //!< DMA channel number + DMA_Channel_TypeDef *DMA_CHx; //!< DMA channel instance + + uint8_t channel_nums[18]; + + // Live config + float real_frequency; + uint32_t real_frequency_int; + uint32_t channels_mask; //!< channels bitfield including tsense and vref + float avg_factor_as_float; + + uint16_t *dma_buffer; //!< malloc'd buffer for the samples + uint8_t nb_channels; //!< nbr of enabled adc channels + uint32_t buf_itemcount; //!< real size of the buffer in samples (adjusted to fit 2x whole multiple of sample group) + + // Trigger state + uint32_t trig_stream_remain; //!< Counter of samples remaining to be sent in the post-trigger stream + uint16_t trig_holdoff_remain; //!< Tmp counter for the currently active hold-off + uint16_t trig_prev_level; //!< Value of the previous sample, used to detect trigger edge + uint32_t stream_startpos; //!< Byte offset in the DMA buffer where the next capture for a stream should start. + //!< Updated in TH/TC and on trigger (after the preceding data is sent as a pretrig buffer) + + enum uadc_opmode opmode; //!< OpMode (state machine state) + float averaging_bins[18]; //!< Averaging buffers, enough space to accommodate all channels (16 external + 2 internal) + uint16_t last_samples[18]; //!< If averaging is disabled, the last captured sample is stored here. + + // Trigger config + uint8_t trigger_source; //!< number of the pin selected as a trigger source + uint32_t pretrig_len; //!< Pre-trigger length, nbr of historical samples to report when trigger occurs + uint32_t trig_len; //!< Trigger length, nbr of samples to report AFTER a trigger occurs + uint16_t trig_level; //!< Triggering level in LSB + uint8_t trig_edge; //!< Which edge we want to trigger on. 1-rising, 2-falling, 3-both + bool auto_rearm; //!< Flag that the trigger should be re-armed after the stream finishes + uint16_t trig_holdoff; //!< Trigger hold-off time, set when configuring the trigger + TF_ID stream_frame_id; //!< Session ID for multi-part stream (response or report) + uint8_t stream_serial; //!< Serial nr of a stream frame + + bool tc_pending; + bool ht_pending; +}; + +/** Allocate data structure and set defaults */ +error_t UADC_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UADC_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UADC_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UADC_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UADC_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UADC_init(Unit *unit); + +/** Tear down the unit */ +void UADC_deInit(Unit *unit); + +/** Configure DMA (buffer count etc) */ +void UADC_SetupDMA(Unit *unit); + +// ------------------------------------------------------------------------ + +/** DMA half/complete handler */ +void UADC_DMA_Handler(void *arg); + +/** ADC eod of sequence handler */ +void UADC_ADC_EOS_Handler(void *arg); + +/** Switch to a different opmode */ +void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode); + +/** Handle trigger - process pre-trigger and start streaming the requested number of samples */ +void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp); + +/** Handle a periodic tick - expiring the hold-off */ +void UADC_updateTick(Unit *unit); + +/** Send a end-of-stream message to PC's stream listener so it can shut down. */ +void UADC_ReportEndOfStream(Unit *unit); + +/** Start a block capture */ +void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id); + +/** Start stream */ +void UADC_StartStream(Unit *unit, TF_ID frame_id); + +/** End stream */ +void UADC_StopStream(Unit *unit); + +/** Configure frequency */ +error_t UADC_SetSampleRate(Unit *unit, uint32_t hertz); + +/** Abort capture */ +void UADC_AbortCapture(Unit *unit); + +#endif //GEX_F072_ADC_INTERNAL_H diff --git a/GexUnits/adc/_adc_settings.c b/GexUnits/adc/_adc_settings.c new file mode 100644 index 0000000..03b32ee --- /dev/null +++ b/GexUnits/adc/_adc_settings.c @@ -0,0 +1,117 @@ +// +// Created by MightyPork on 2018/02/03. +// +// ADC unit settings reading / parsing +// + +#include "platform.h" +#include "unit_base.h" + +#define ADC_INTERNAL +#include "_adc_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UADC_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->cfg.channels = pp_u32(pp); + priv->cfg.sample_time = pp_u8(pp); + priv->cfg.frequency = pp_u32(pp); + priv->cfg.buffer_size = pp_u32(pp); + priv->cfg.averaging_factor = pp_u16(pp); + + if (version >= 1) { + priv->cfg.enable_averaging = pp_bool(pp); + } +} + +/** Write to a binary buffer for storing in Flash */ +void UADC_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 1); // version + + pb_u32(pb, priv->cfg.channels); + pb_u8(pb, priv->cfg.sample_time); + pb_u32(pb, priv->cfg.frequency); + pb_u32(pb, priv->cfg.buffer_size); + pb_u16(pb, priv->cfg.averaging_factor); + pb_bool(pb, priv->cfg.enable_averaging); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UADC_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "channels")) { + priv->cfg.channels = cfg_pinmask_parse_32(value, &suc); + } + else if (streq(key, "sample_time")) { + priv->cfg.sample_time = cfg_u8_parse(value, &suc); + if (priv->cfg.sample_time > 7) return E_BAD_VALUE; + } + else if (streq(key, "frequency")) { + priv->cfg.frequency = cfg_u32_parse(value, &suc); + } + else if (streq(key, "buffer_size")) { + priv->cfg.buffer_size = cfg_u32_parse(value, &suc); + } + else if (streq(key, "avg_factor")) { + priv->cfg.averaging_factor = cfg_u16_parse(value, &suc); + if (priv->cfg.averaging_factor > 1000) return E_BAD_VALUE; + } + else if (streq(key, "averaging")) { + priv->cfg.enable_averaging = cfg_bool_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UADC_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Enabled channels, comma separated"); + iw_comment(iw, " 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17"); + iw_comment(iw, "A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 C0 C1 C2 C3 C4 C5 Tsens Vref"); + iw_entry_s(iw, "channels", cfg_pinmask_encode(priv->cfg.channels, unit_tmp512, true)); + + iw_cmt_newline(iw); + iw_comment(iw, "Sampling time (0-7)"); + iw_entry_d(iw, "sample_time", priv->cfg.sample_time); + + iw_comment(iw, "Sampling frequency (Hz)"); + iw_entry_d(iw, "frequency", priv->cfg.frequency); + + iw_cmt_newline(iw); + iw_comment(iw, "Sample buffer size"); + iw_comment(iw, "- shared by all enabled channels"); + iw_comment(iw, "- defines the maximum pre-trigger size (divide by # of channels)"); + iw_comment(iw, "- captured data is sent in half-buffer chunks"); + iw_comment(iw, "- buffer overrun aborts the data capture"); + iw_entry_d(iw, "buffer_size", priv->cfg.buffer_size); + + iw_cmt_newline(iw); + iw_comment(iw, "Enable continuous sampling with averaging"); + iw_comment(iw, "Caution: This can cause DAC output glitches"); + iw_entry_s(iw, "averaging", str_yn(priv->cfg.enable_averaging)); + iw_comment(iw, "Exponential averaging coefficient (permil, range 0-1000 ~ 0.000-1.000)"); + iw_comment(iw, "- used formula: y[t]=(1-k)*y[t-1]+k*u[t]"); + iw_comment(iw, "- not available when a capture is running"); + iw_entry_d(iw, "avg_factor", priv->cfg.averaging_factor); +} + diff --git a/GexUnits/adc/unit_adc.c b/GexUnits/adc/unit_adc.c new file mode 100644 index 0000000..6d05496 --- /dev/null +++ b/GexUnits/adc/unit_adc.c @@ -0,0 +1,428 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_adc.h" + +#define ADC_INTERNAL +#include "_adc_internal.h" + +// ------------------------------------------------------------------------ + +enum AdcCmd_ { + CMD_READ_RAW = 0, + CMD_READ_SMOOTHED = 1, + CMD_READ_CAL_CONSTANTS = 2, + + CMD_GET_ENABLED_CHANNELS = 10, + CMD_GET_SAMPLE_RATE = 11, + + CMD_SETUP_TRIGGER = 20, + CMD_ARM = 21, + CMD_DISARM = 22, + CMD_ABORT = 23, // abort any ongoing capture or stream + CMD_FORCE_TRIGGER = 24, + CMD_BLOCK_CAPTURE = 25, + CMD_STREAM_START = 26, + CMD_STREAM_STOP = 27, + CMD_SET_SMOOTHING_FACTOR = 28, + CMD_SET_SAMPLE_RATE = 29, + CMD_ENABLE_CHANNELS = 30, + CMD_SET_SAMPLE_TIME = 31, +}; + +/** Handle a request message */ +static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + struct priv *priv = unit->data; + PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); + + switch (command) { + /** + * Get enabled channels. + * Response: bytes with indices of enabled channels, ascending order. + */ + case CMD_GET_ENABLED_CHANNELS: + for (uint8_t i = 0; i < 18; i++) { + if (priv->channels_mask & (1 << i)) { + pb_u8(&pb, i); + } + } + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Set the sample rate in Hz + * plad: hz:u32 + */ + case CMD_SET_SAMPLE_RATE: + { + uint32_t freq = pp_u32(pp); + if (freq == 0) return E_BAD_VALUE; + + TRY(UADC_SetSampleRate(unit, freq)); + } + // Pass through - send back the obtained sample rate + /** + * Read the real used frequency, expressed as float. + * May differ from the configured or requested value due to prescaller limitations. + */ + case CMD_GET_SAMPLE_RATE: + pb_u32(&pb, priv->real_frequency_int); + pb_float(&pb, priv->real_frequency); + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Set smoothing factor 0-1000. + * pld: u16:factor + */ + case CMD_SET_SMOOTHING_FACTOR: + { + uint16_t fac = pp_u16(pp); + if (fac > 1000) return E_BAD_VALUE; + priv->avg_factor_as_float = fac / 1000.0f; + } + return E_SUCCESS; + + /** + * Set sample time + * pld: u8:0-7 + */ + case CMD_SET_SAMPLE_TIME: + { + uint8_t tim = pp_u8(pp); + if (tim > 7) return E_BAD_VALUE; + UADC_SwitchMode(unit, ADC_OPMODE_UNINIT); + { + LL_ADC_SetSamplingTimeCommonChannels(priv->ADCx, LL_ADC_SAMPLETIMES[tim]); + } + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + } + return E_SUCCESS; + + /** Read ADC calibration constants */ + case CMD_READ_CAL_CONSTANTS: + { + pb_u16(&pb, *VREFINT_CAL_ADDR); // VREFINT_CAL + pb_u16(&pb, VREFINT_CAL_VREF); // Vref pin voltage during calibration (usually bonded to Vdd) + + pb_u16(&pb, *TEMPSENSOR_CAL1_ADDR); // TEMPSENSOR_CAL1 + pb_u16(&pb, *TEMPSENSOR_CAL2_ADDR); // TEMPSENSOR_CAL2 + pb_u8(&pb, TEMPSENSOR_CAL1_TEMP); // temperature for CAL1 + pb_u8(&pb, TEMPSENSOR_CAL2_TEMP); // temperature for CAL2 + pb_u16(&pb, TEMPSENSOR_CAL_VREFANALOG); // VREFINT_CAL_VREF - Vref pin voltage during calibration (usually bonded to Vdd) + + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + } + return E_SUCCESS; + + /** + * Enable channels. The channels must've been configured in the settings (except ch 16 and 17 which are available always) + * pld: u32: bitmap of channels + */ + case CMD_ENABLE_CHANNELS: + { + uint32_t new_channels = pp_u32(pp); + + // this tears down the peripherals sufficiently so we can re-configure them. Going back to IDLE re-inits this + UADC_SwitchMode(unit, ADC_OPMODE_UNINIT); + + uint32_t illegal_channels = new_channels & ~(priv->cfg.channels | (1<<16) | (1<<17)); // 16 and 17 may be enabled always + if (illegal_channels != 0) { + com_respond_str(MSG_ERROR, frame_id, "Some requested channels not available"); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + return E_FAILURE; + } + + uint8_t nb_channels = 0; + // count the enabled channels + for(int i = 0; i < 32; i++) { + if (new_channels & (1<channel_nums[nb_channels] = (uint8_t) i; + nb_channels++; + } + } + + if (nb_channels == 0) { + com_respond_str(MSG_ERROR, frame_id, "Need at least 1 channel"); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + return E_FAILURE; + } + + if (priv->cfg.buffer_size < nb_channels * 2) { + com_respond_str(MSG_ERROR, frame_id, "Insufficient buf size"); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + return E_BAD_CONFIG; + } + + priv->nb_channels = nb_channels; + priv->ADCx->CHSELR = new_channels; // apply it to the ADC + priv->channels_mask = new_channels; + + UADC_SetupDMA(unit); + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + } + return E_SUCCESS; + + /** + * Read raw values from the last measurement. + * Response: interleaved (u8:channel, u16:value) for all channels + */ + case CMD_READ_RAW: + if(priv->opmode != ADC_OPMODE_IDLE && priv->opmode != ADC_OPMODE_ARMED) { + return E_BUSY; + } + + for (uint8_t i = 0; i < 18; i++) { + if (priv->channels_mask & (1 << i)) { + pb_u16(&pb, priv->last_samples[i]); + } + } + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Read smoothed values. + * Response: interleaved (u8:channel, f32:value) for all channels + */ + case CMD_READ_SMOOTHED: + if(priv->opmode != ADC_OPMODE_IDLE && priv->opmode != ADC_OPMODE_ARMED) { + return E_BUSY; + } + + if (! priv->cfg.enable_averaging) { + com_respond_str(MSG_ERROR, frame_id, "Averaging disabled"); + return E_FAILURE; + } + + if (priv->real_frequency_int > UADC_MAX_FREQ_FOR_AVERAGING) { + com_respond_str(MSG_ERROR, frame_id, "Too fast for averaging"); + return E_FAILURE; + } + + for (uint8_t i = 0; i < 18; i++) { + if (priv->channels_mask & (1 << i)) { + pb_float(&pb, priv->averaging_bins[i]); + } + } + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Configure a trigger. This is legal only if the current state is IDLE or ARMED (will re-arm). + * + * Payload: + * u8 - source channel + * u16 - triggering level + * u8 - edge to trigger on: 1-rising, 2-falling, 3-both + * u16 - pre-trigger samples count + * u32 - post-trigger samples count + * u16 - trigger hold-off in ms (dead time after firing, before it cna fire again if armed) + * u8(bool) - auto re-arm after firing and completing the capture + */ + case CMD_SETUP_TRIGGER: + adc_dbg("> Setup trigger"); + if (priv->opmode != ADC_OPMODE_IDLE && + priv->opmode != ADC_OPMODE_ARMED && + priv->opmode != ADC_OPMODE_REARM_PENDING) { + return E_BUSY; + } + + { + const uint8_t source = pp_u8(pp); + const uint16_t level = pp_u16(pp); + const uint8_t edge = pp_u8(pp); + const uint32_t pretrig = pp_u32(pp); + const uint32_t count = pp_u32(pp); + const uint16_t holdoff = pp_u16(pp); + const bool auto_rearm = pp_bool(pp); + + if (source > UADC_MAX_CHANNEL) { + com_respond_str(MSG_ERROR, frame_id, "Invalid trig source"); + return E_FAILURE; + } + + if (0 == (priv->channels_mask & (1 << source))) { + com_respond_str(MSG_ERROR, frame_id, "Channel not enabled"); + return E_FAILURE; + } + + if (level > 4095) { + com_respond_str(MSG_ERROR, frame_id, "Level out of range (0-4095)"); + return E_FAILURE; + } + + if (edge == 0 || edge > 3) { + com_respond_str(MSG_ERROR, frame_id, "Bad edge"); + return E_FAILURE; + } + + // XXX the max size may be too much + const uint32_t max_pretrig = (priv->buf_itemcount / priv->nb_channels); + if (pretrig > max_pretrig) { + com_respond_snprintf(frame_id, MSG_ERROR, + "Pretrig too large (max %d)", (int) max_pretrig); + return E_FAILURE; + } + + priv->trigger_source = source; + priv->trig_level = level; + priv->trig_prev_level = priv->last_samples[source]; + priv->trig_edge = edge; + priv->pretrig_len = pretrig; + priv->trig_len = count; + priv->trig_holdoff = holdoff; + priv->auto_rearm = auto_rearm; + } + return E_SUCCESS; + + /** + * Arm (permissible only if idle and the trigger is configured) + */ + case CMD_ARM: + adc_dbg("> Arm"); + uint8_t sticky = pp_u8(pp); + + if(priv->opmode == ADC_OPMODE_ARMED || priv->opmode == ADC_OPMODE_REARM_PENDING) { + // We are armed or will re-arm promptly, act like the call succeeded + // The auto flag is set regardless + } else { + if (priv->opmode != ADC_OPMODE_IDLE) { + return E_BUSY; // capture in progress + } + + if (priv->trig_len == 0) { + com_respond_str(MSG_ERROR, frame_id, "Trigger not configured."); + return E_FAILURE; + } + + UADC_SwitchMode(unit, ADC_OPMODE_ARMED); + } + + if (sticky != 255) { + priv->auto_rearm = (bool)sticky; + } + + return E_SUCCESS; + + /** + * Dis-arm. Permissible only when idle or armed. + * Switches to idle. + */ + case CMD_DISARM: + adc_dbg("> Disarm"); + + priv->auto_rearm = false; + + if(priv->opmode == ADC_OPMODE_IDLE) { + return E_SUCCESS; // already idle, success - no work to do + } + + // capture in progress + if (priv->opmode != ADC_OPMODE_ARMED && + priv->opmode != ADC_OPMODE_REARM_PENDING) { + // Capture in progress, we already cleared auto rearm, so we're done for now + // auto_rearm is checked in the EOS isr and if cleared, does not re-arm. + return E_SUCCESS; + } + + UADC_SwitchMode(unit, ADC_OPMODE_IDLE); + return E_SUCCESS; + + /** + * Abort any ongoing capture and dis-arm. + */ + case CMD_ABORT:; + adc_dbg("> Abort capture"); + UADC_AbortCapture(unit); + return E_SUCCESS; + + /** + * Force a trigger (complete with pre-trigger capture and hold-off) + * The reported edge will be 0b11, here meaning "manual trigger" + */ + case CMD_FORCE_TRIGGER: + adc_dbg("> Force trigger"); + // This is similar to block capture, but includes the pre-trig buffer and has fixed size based on trigger config + // FORCE is useful for checking if the trigger is set up correctly + if (priv->opmode != ADC_OPMODE_ARMED && + priv->opmode != ADC_OPMODE_IDLE && + priv->opmode != ADC_OPMODE_REARM_PENDING) return E_BUSY; + + if (priv->trig_len == 0) { + com_respond_str(MSG_ERROR, frame_id, "Trigger not configured."); + return E_FAILURE; + } + + UADC_HandleTrigger(unit, 0b11, PTIM_GetMicrotime()); + return E_SUCCESS; + + /** + * Start a block capture (like manual trigger, but without pre-trigger and arming) + * + * Payload: + * u32 - sample count (for each channel) + */ + case CMD_BLOCK_CAPTURE: + adc_dbg("> Block cpt"); + if (priv->opmode != ADC_OPMODE_ARMED && + priv->opmode != ADC_OPMODE_REARM_PENDING && + priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; + + uint32_t count = pp_u32(pp); + + UADC_StartBlockCapture(unit, count, frame_id); + return E_SUCCESS; + + /** + * Start streaming (like block capture, but unlimited) + * The stream can be terminated by the stop command. + */ + case CMD_STREAM_START: + adc_dbg("> Stream ON"); + if (priv->opmode != ADC_OPMODE_ARMED && + priv->opmode != ADC_OPMODE_REARM_PENDING && + priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; + + UADC_StartStream(unit, frame_id); + return E_SUCCESS; + + /** + * Stop a stream. + */ + case CMD_STREAM_STOP: + adc_dbg("> Stream OFF"); + if (priv->opmode != ADC_OPMODE_STREAM) { + com_respond_str(MSG_ERROR, frame_id, "Not streaming"); + return E_FAILURE; + } + + UADC_StopStream(unit); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_ADC = { + .name = "ADC", + .description = "Analog/digital converter", + // Settings + .preInit = UADC_preInit, + .cfgLoadBinary = UADC_loadBinary, + .cfgWriteBinary = UADC_writeBinary, + .cfgLoadIni = UADC_loadIni, + .cfgWriteIni = UADC_writeIni, + // Init + .init = UADC_init, + .deInit = UADC_deInit, + // Function + .handleRequest = UADC_handleRequest, + .updateTick = UADC_updateTick, +}; diff --git a/GexUnits/adc/unit_adc.h b/GexUnits/adc/unit_adc.h new file mode 100644 index 0000000..016798f --- /dev/null +++ b/GexUnits/adc/unit_adc.h @@ -0,0 +1,15 @@ +// +// Created by MightyPork on 2017/11/25. +// +// ADC unit with several DSO-like features, like triggering, pre-trigger, block capture, +// streaming, smoothing... +// + +#ifndef U_TPL_H +#define U_TPL_H + +#include "unit.h" + +extern const UnitDriver UNIT_ADC; + +#endif //U_TPL_H diff --git a/GexUnits/dac/_dac_api.c b/GexUnits/dac/_dac_api.c new file mode 100644 index 0000000..529f326 --- /dev/null +++ b/GexUnits/dac/_dac_api.c @@ -0,0 +1,84 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_dac.h" + +#define DAC_INTERNAL +#include "_dac_internal.h" + +/** + * Re-configure the + * @param unit + */ +void UDAC_Reconfigure(Unit *unit) +{ + struct priv *priv = unit->data; + + // back-up IT state + bool IT_enabled = (bool) LL_TIM_IsEnabledIT_UPDATE(priv->TIMx); + if (IT_enabled) { + LL_TIM_DisableIT_UPDATE(priv->TIMx); + } + + // ... + DAC->CR &= ~(DAC_CR_EN1 | DAC_CR_EN2); + + uint32_t CR = 0; + if (priv->cfg.ch[0].enable) { + CR |= + (priv->cfg.ch[0].buffered ? 0 : DAC_CR_BOFF1) | + (priv->ch[0].noise_type << DAC_CR_WAVE1_Pos) | + (priv->ch[0].noise_level & 0xF) << DAC_CR_MAMP1_Pos | + DAC_CR_TEN1 | + (0b111 << DAC_CR_TSEL1_Pos); // software trigger; + + CR |= DAC_CR_EN1; + } + + if (priv->cfg.ch[1].enable) { + CR |= + (priv->cfg.ch[1].buffered ? 0 : DAC_CR_BOFF2) | + (priv->ch[1].noise_type << DAC_CR_WAVE2_Pos) | + (priv->ch[1].noise_level & 0xF) << DAC_CR_MAMP2_Pos | + DAC_CR_TEN2 | + (0b111 << DAC_CR_TSEL2_Pos); // software trigger + CR |= DAC_CR_EN2; + } + + DAC->CR = CR; + // ... + + if (IT_enabled) { + LL_TIM_EnableIT_UPDATE(priv->TIMx); + } +} + +error_t UDAC_SetFreq(Unit *unit, int channel, float freq) +{ + struct priv *priv = unit->data; + + priv->ch[channel].increment = (uint32_t) roundf(25.0f * freq * UDAC_VALUE_COUNT * // FIXME constant seems derived from the divide ...? + ((float)UDAC_TIM_FREQ_DIVIDER / PLAT_AHB_MHZ) + ); + +// dbg("Ch %d: Computed increment: %d", channel+1, (int)priv->ch[channel].increment); + + return E_SUCCESS; +} + +void UDAC_ToggleTimerIfNeeded(Unit *unit) +{ + struct priv *priv = unit->data; + + if (priv->ch[0].waveform == UDAC_WAVE_DC && priv->ch[1].waveform == UDAC_WAVE_DC) { + LL_TIM_DisableCounter(priv->TIMx); + + // manually call the interrupt function to update the level + UDAC_HandleIT(unit); + } else { + LL_TIM_EnableCounter(priv->TIMx); + } +} diff --git a/GexUnits/dac/_dac_core.c b/GexUnits/dac/_dac_core.c new file mode 100644 index 0000000..5414a20 --- /dev/null +++ b/GexUnits/dac/_dac_core.c @@ -0,0 +1,392 @@ +// +// Created by MightyPork on 2018/03/10. +// + +#include "platform.h" +#include "unit_dac.h" + +#define DAC_INTERNAL +#include "_dac_internal.h" + + +#pragma GCC push_options +#pragma GCC optimize ("O2") +void UDAC_HandleIT(void *arg) +{ + Unit *unit = arg; + struct priv *priv = unit->data; + LL_TIM_ClearFlag_UPDATE(priv->TIMx); + + // TODO optimize to allow faster update speed + + uint16_t vals[2]; + for (int i = 0; i < 2; i++) { + struct udac_channel_live *const ch = &priv->ch[i]; + + if (ch->waveform == UDAC_WAVE_DC) { + // skip the whole counter thing for DC + vals[i] = ch->dc_level; + continue; + } + + ch->counter += ch->increment; + const uint16_t index = (uint16_t) (ch->counter >> UDAC_INDEX_SHIFT); + + switch (ch->waveform) { + case UDAC_WAVE_SINE: { + const uint32_t phase = index >> (UDAC_INDEX_WIDTH - 2); + + uint32_t pos = (uint32_t) index & (((1<<(UDAC_INDEX_WIDTH - 2)) - 1)); + + if (phase & 0b01) { + // 2nd and 4th quarter (01, 11) + pos = (UDAC_VALUE_COUNT/4 - 1) - pos; + } + + uint32_t value; + + /* unpack */ + const uint32_t nthbyte = (pos * 3) / 2; + const uint8_t topbyte = LUT_sine_8192_quad_packed[nthbyte]; + const uint8_t botbyte = LUT_sine_8192_quad_packed[nthbyte + 1]; + if (pos & 1) { + // odd index + value = (uint32_t) (((topbyte & 0xF) << 8) | botbyte); + } + else { + value = (uint32_t) ((topbyte << 4) | ((botbyte & 0xF0) >> 4)); + } + /* end unpack -> value */ + + if (phase & 0b10) { + // 3rd and 4th quarter (10, 11) + value = 2047 - value; + } + else { + // 1st and 2nd quarter (00, 01) + value = 2048 + value; + } + + vals[i] = (uint16_t) value; + + break; + } + + case UDAC_WAVE_RECTANGLE: + if (index < ch->rectangle_ontime) { + vals[i] = ch->rectangle_high; + } else { + vals[i] = ch->rectangle_low; + } + break; + + case UDAC_WAVE_SAWTOOTH_UP: + vals[i] = (uint16_t) (index / 2); // for 8192 steps, this will produce 0-4095 + break; + + case UDAC_WAVE_SAWTOOTH_DOWN: + vals[i] = (uint16_t) (4095 - (index / 2)); // for 8192 steps, this will produce 0-4095 + break; + + case UDAC_WAVE_TRIANGLE: { + if (index < 4096) { + vals[i] = index; + } + else { + vals[i] = (uint16_t) (8191 - index); + } + break; + } + + default: + vals[i] = 0; // this shouldn't happen + break; + } // end select waveform + } // end for 0,1 + + DAC->DHR12RD = (vals[1] << 16) | vals[0]; + + // Trigger a conversion + DAC->SWTRIGR = (DAC_SWTR_CH1 | DAC_SWTR_CH2); +} +#pragma GCC pop_options + + +/** + * This is the first 1/4 of a 12-bit sine wave sampled in 8192 points, each two points packed into three bytes (saves 1/4 of space used by uint16_t's) + * The array holds 2048 data points which can be easily offset / flipped to cover the entire waveform. + * + * |<-->| + * _---_ + * / \ + * / \ + * /---------\---------/ + * \ / + * \_ _/ + * --- + */ +#if 0 // this is rail to rail, but the dac doesn't work well at the rails and it gets flattened +const uint8_t LUT_sine_8192_quad_packed[] = { + 0x00, 0x00, 0x02, 0x00, 0x30, 0x05, 0x00, 0x60, 0x08, 0x00, 0x90, 0x0B, 0x00, 0xD0, 0x0E, 0x01, 0x00, 0x11, 0x01, 0x30, 0x14, 0x01, 0x60, 0x18, + 0x01, 0x90, 0x1B, 0x01, 0xC0, 0x1E, 0x01, 0xF0, 0x21, 0x02, 0x30, 0x24, 0x02, 0x60, 0x27, 0x02, 0x90, 0x2A, 0x02, 0xC0, 0x2E, 0x02, 0xF0, 0x31, + 0x03, 0x20, 0x34, 0x03, 0x50, 0x37, 0x03, 0x90, 0x3A, 0x03, 0xC0, 0x3D, 0x03, 0xF0, 0x40, 0x04, 0x20, 0x43, 0x04, 0x50, 0x47, 0x04, 0x80, 0x4A, + 0x04, 0xB0, 0x4D, 0x04, 0xE0, 0x50, 0x05, 0x20, 0x53, 0x05, 0x50, 0x56, 0x05, 0x80, 0x59, 0x05, 0xB0, 0x5D, 0x05, 0xE0, 0x60, 0x06, 0x10, 0x63, + 0x06, 0x40, 0x66, 0x06, 0x80, 0x69, 0x06, 0xB0, 0x6C, 0x06, 0xE0, 0x6F, 0x07, 0x10, 0x73, 0x07, 0x40, 0x76, 0x07, 0x70, 0x79, 0x07, 0xA0, 0x7C, + 0x07, 0xE0, 0x7F, 0x08, 0x10, 0x82, 0x08, 0x40, 0x85, 0x08, 0x70, 0x88, 0x08, 0xA0, 0x8C, 0x08, 0xD0, 0x8F, 0x09, 0x00, 0x92, 0x09, 0x30, 0x95, + 0x09, 0x70, 0x98, 0x09, 0xA0, 0x9B, 0x09, 0xD0, 0x9E, 0x0A, 0x00, 0xA2, 0x0A, 0x30, 0xA5, 0x0A, 0x60, 0xA8, 0x0A, 0x90, 0xAB, 0x0A, 0xC0, 0xAE, + 0x0B, 0x00, 0xB1, 0x0B, 0x30, 0xB4, 0x0B, 0x60, 0xB7, 0x0B, 0x90, 0xBB, 0x0B, 0xC0, 0xBE, 0x0B, 0xF0, 0xC1, 0x0C, 0x20, 0xC4, 0x0C, 0x60, 0xC7, + 0x0C, 0x90, 0xCA, 0x0C, 0xC0, 0xCD, 0x0C, 0xF0, 0xD0, 0x0D, 0x20, 0xD4, 0x0D, 0x50, 0xD7, 0x0D, 0x80, 0xDA, 0x0D, 0xB0, 0xDD, 0x0D, 0xF0, 0xE0, + 0x0E, 0x20, 0xE3, 0x0E, 0x50, 0xE6, 0x0E, 0x80, 0xE9, 0x0E, 0xB0, 0xED, 0x0E, 0xE0, 0xF0, 0x0F, 0x10, 0xF3, 0x0F, 0x40, 0xF6, 0x0F, 0x70, 0xF9, + 0x0F, 0xB0, 0xFC, 0x0F, 0xE0, 0xFF, 0x10, 0x11, 0x02, 0x10, 0x41, 0x05, 0x10, 0x71, 0x09, 0x10, 0xA1, 0x0C, 0x10, 0xD1, 0x0F, 0x11, 0x01, 0x12, + 0x11, 0x31, 0x15, 0x11, 0x71, 0x18, 0x11, 0xA1, 0x1B, 0x11, 0xD1, 0x1E, 0x12, 0x01, 0x21, 0x12, 0x31, 0x25, 0x12, 0x61, 0x28, 0x12, 0x91, 0x2B, + 0x12, 0xC1, 0x2E, 0x12, 0xF1, 0x31, 0x13, 0x31, 0x34, 0x13, 0x61, 0x37, 0x13, 0x91, 0x3A, 0x13, 0xC1, 0x3D, 0x13, 0xF1, 0x41, 0x14, 0x21, 0x44, + 0x14, 0x51, 0x47, 0x14, 0x81, 0x4A, 0x14, 0xB1, 0x4D, 0x14, 0xE1, 0x50, 0x15, 0x21, 0x53, 0x15, 0x51, 0x56, 0x15, 0x81, 0x59, 0x15, 0xB1, 0x5C, + 0x15, 0xE1, 0x60, 0x16, 0x11, 0x63, 0x16, 0x41, 0x66, 0x16, 0x71, 0x69, 0x16, 0xA1, 0x6C, 0x16, 0xD1, 0x6F, 0x17, 0x11, 0x72, 0x17, 0x41, 0x75, + 0x17, 0x71, 0x78, 0x17, 0xA1, 0x7B, 0x17, 0xD1, 0x7E, 0x18, 0x01, 0x81, 0x18, 0x31, 0x85, 0x18, 0x61, 0x88, 0x18, 0x91, 0x8B, 0x18, 0xC1, 0x8E, + 0x18, 0xF1, 0x91, 0x19, 0x21, 0x94, 0x19, 0x61, 0x97, 0x19, 0x91, 0x9A, 0x19, 0xC1, 0x9D, 0x19, 0xF1, 0xA0, 0x1A, 0x21, 0xA3, 0x1A, 0x51, 0xA6, + 0x1A, 0x81, 0xA9, 0x1A, 0xB1, 0xAD, 0x1A, 0xE1, 0xB0, 0x1B, 0x11, 0xB3, 0x1B, 0x41, 0xB6, 0x1B, 0x71, 0xB9, 0x1B, 0xA1, 0xBC, 0x1B, 0xD1, 0xBF, + 0x1C, 0x11, 0xC2, 0x1C, 0x41, 0xC5, 0x1C, 0x71, 0xC8, 0x1C, 0xA1, 0xCB, 0x1C, 0xD1, 0xCE, 0x1D, 0x01, 0xD1, 0x1D, 0x31, 0xD4, 0x1D, 0x61, 0xD7, + 0x1D, 0x91, 0xDB, 0x1D, 0xC1, 0xDE, 0x1D, 0xF1, 0xE1, 0x1E, 0x21, 0xE4, 0x1E, 0x51, 0xE7, 0x1E, 0x81, 0xEA, 0x1E, 0xB1, 0xED, 0x1E, 0xE1, 0xF0, + 0x1F, 0x11, 0xF3, 0x1F, 0x41, 0xF6, 0x1F, 0x71, 0xF9, 0x1F, 0xB1, 0xFC, 0x1F, 0xE1, 0xFF, 0x20, 0x12, 0x02, 0x20, 0x42, 0x05, 0x20, 0x72, 0x08, + 0x20, 0xA2, 0x0B, 0x20, 0xD2, 0x0E, 0x21, 0x02, 0x11, 0x21, 0x32, 0x14, 0x21, 0x62, 0x17, 0x21, 0x92, 0x1A, 0x21, 0xC2, 0x1D, 0x21, 0xF2, 0x20, + 0x22, 0x22, 0x23, 0x22, 0x52, 0x26, 0x22, 0x82, 0x2A, 0x22, 0xB2, 0x2D, 0x22, 0xE2, 0x30, 0x23, 0x12, 0x33, 0x23, 0x42, 0x36, 0x23, 0x72, 0x39, + 0x23, 0xA2, 0x3C, 0x23, 0xD2, 0x3F, 0x24, 0x02, 0x42, 0x24, 0x32, 0x45, 0x24, 0x62, 0x48, 0x24, 0x92, 0x4B, 0x24, 0xC2, 0x4E, 0x24, 0xF2, 0x51, + 0x25, 0x22, 0x54, 0x25, 0x52, 0x57, 0x25, 0x82, 0x5A, 0x25, 0xB2, 0x5D, 0x25, 0xE2, 0x60, 0x26, 0x12, 0x63, 0x26, 0x42, 0x66, 0x26, 0x72, 0x69, + 0x26, 0xA2, 0x6C, 0x26, 0xD2, 0x6F, 0x27, 0x02, 0x72, 0x27, 0x32, 0x75, 0x27, 0x62, 0x78, 0x27, 0x92, 0x7B, 0x27, 0xC2, 0x7E, 0x27, 0xF2, 0x81, + 0x28, 0x22, 0x84, 0x28, 0x52, 0x87, 0x28, 0x82, 0x8A, 0x28, 0xB2, 0x8D, 0x28, 0xE2, 0x90, 0x29, 0x12, 0x92, 0x29, 0x42, 0x95, 0x29, 0x72, 0x98, + 0x29, 0xA2, 0x9B, 0x29, 0xD2, 0x9E, 0x2A, 0x02, 0xA1, 0x2A, 0x32, 0xA4, 0x2A, 0x62, 0xA7, 0x2A, 0x92, 0xAA, 0x2A, 0xC2, 0xAD, 0x2A, 0xF2, 0xB0, + 0x2B, 0x22, 0xB3, 0x2B, 0x52, 0xB6, 0x2B, 0x82, 0xB9, 0x2B, 0xA2, 0xBC, 0x2B, 0xD2, 0xBF, 0x2C, 0x02, 0xC2, 0x2C, 0x32, 0xC5, 0x2C, 0x62, 0xC8, + 0x2C, 0x92, 0xCB, 0x2C, 0xC2, 0xCE, 0x2C, 0xF2, 0xD1, 0x2D, 0x22, 0xD4, 0x2D, 0x52, 0xD6, 0x2D, 0x82, 0xD9, 0x2D, 0xB2, 0xDC, 0x2D, 0xE2, 0xDF, + 0x2E, 0x12, 0xE2, 0x2E, 0x42, 0xE5, 0x2E, 0x72, 0xE8, 0x2E, 0x92, 0xEB, 0x2E, 0xC2, 0xEE, 0x2E, 0xF2, 0xF1, 0x2F, 0x22, 0xF4, 0x2F, 0x52, 0xF7, + 0x2F, 0x82, 0xFA, 0x2F, 0xB2, 0xFC, 0x2F, 0xE2, 0xFF, 0x30, 0x13, 0x02, 0x30, 0x43, 0x05, 0x30, 0x73, 0x08, 0x30, 0xA3, 0x0B, 0x30, 0xC3, 0x0E, + 0x30, 0xF3, 0x11, 0x31, 0x23, 0x14, 0x31, 0x53, 0x17, 0x31, 0x83, 0x19, 0x31, 0xB3, 0x1C, 0x31, 0xE3, 0x1F, 0x32, 0x13, 0x22, 0x32, 0x43, 0x25, + 0x32, 0x73, 0x28, 0x32, 0x93, 0x2B, 0x32, 0xC3, 0x2E, 0x32, 0xF3, 0x31, 0x33, 0x23, 0x33, 0x33, 0x53, 0x36, 0x33, 0x83, 0x39, 0x33, 0xB3, 0x3C, + 0x33, 0xE3, 0x3F, 0x34, 0x03, 0x42, 0x34, 0x33, 0x45, 0x34, 0x63, 0x48, 0x34, 0x93, 0x4A, 0x34, 0xC3, 0x4D, 0x34, 0xF3, 0x50, 0x35, 0x23, 0x53, + 0x35, 0x43, 0x56, 0x35, 0x73, 0x59, 0x35, 0xA3, 0x5C, 0x35, 0xD3, 0x5E, 0x36, 0x03, 0x61, 0x36, 0x33, 0x64, 0x36, 0x63, 0x67, 0x36, 0x83, 0x6A, + 0x36, 0xB3, 0x6D, 0x36, 0xE3, 0x6F, 0x37, 0x13, 0x72, 0x37, 0x43, 0x75, 0x37, 0x73, 0x78, 0x37, 0x93, 0x7B, 0x37, 0xC3, 0x7E, 0x37, 0xF3, 0x80, + 0x38, 0x23, 0x83, 0x38, 0x53, 0x86, 0x38, 0x73, 0x89, 0x38, 0xA3, 0x8C, 0x38, 0xD3, 0x8F, 0x39, 0x03, 0x91, 0x39, 0x33, 0x94, 0x39, 0x63, 0x97, + 0x39, 0x83, 0x9A, 0x39, 0xB3, 0x9D, 0x39, 0xE3, 0x9F, 0x3A, 0x13, 0xA2, 0x3A, 0x43, 0xA5, 0x3A, 0x63, 0xA8, 0x3A, 0x93, 0xAB, 0x3A, 0xC3, 0xAD, + 0x3A, 0xF3, 0xB0, 0x3B, 0x23, 0xB3, 0x3B, 0x43, 0xB6, 0x3B, 0x73, 0xB8, 0x3B, 0xA3, 0xBB, 0x3B, 0xD3, 0xBE, 0x3B, 0xF3, 0xC1, 0x3C, 0x23, 0xC4, + 0x3C, 0x53, 0xC6, 0x3C, 0x83, 0xC9, 0x3C, 0xA3, 0xCC, 0x3C, 0xD3, 0xCF, 0x3D, 0x03, 0xD1, 0x3D, 0x33, 0xD4, 0x3D, 0x63, 0xD7, 0x3D, 0x83, 0xDA, + 0x3D, 0xB3, 0xDC, 0x3D, 0xE3, 0xDF, 0x3E, 0x13, 0xE2, 0x3E, 0x33, 0xE5, 0x3E, 0x63, 0xE7, 0x3E, 0x93, 0xEA, 0x3E, 0xB3, 0xED, 0x3E, 0xE3, 0xF0, + 0x3F, 0x13, 0xF2, 0x3F, 0x43, 0xF5, 0x3F, 0x63, 0xF8, 0x3F, 0x93, 0xFB, 0x3F, 0xC3, 0xFD, 0x3F, 0xF4, 0x00, 0x40, 0x14, 0x03, 0x40, 0x44, 0x05, + 0x40, 0x74, 0x08, 0x40, 0x94, 0x0B, 0x40, 0xC4, 0x0E, 0x40, 0xF4, 0x10, 0x41, 0x24, 0x13, 0x41, 0x44, 0x16, 0x41, 0x74, 0x18, 0x41, 0xA4, 0x1B, + 0x41, 0xC4, 0x1E, 0x41, 0xF4, 0x20, 0x42, 0x24, 0x23, 0x42, 0x44, 0x26, 0x42, 0x74, 0x28, 0x42, 0xA4, 0x2B, 0x42, 0xC4, 0x2E, 0x42, 0xF4, 0x30, + 0x43, 0x24, 0x33, 0x43, 0x54, 0x36, 0x43, 0x74, 0x39, 0x43, 0xA4, 0x3B, 0x43, 0xD4, 0x3E, 0x43, 0xF4, 0x40, 0x44, 0x24, 0x43, 0x44, 0x44, 0x46, + 0x44, 0x74, 0x48, 0x44, 0xA4, 0x4B, 0x44, 0xC4, 0x4E, 0x44, 0xF4, 0x50, 0x45, 0x24, 0x53, 0x45, 0x44, 0x56, 0x45, 0x74, 0x58, 0x45, 0xA4, 0x5B, + 0x45, 0xC4, 0x5E, 0x45, 0xF4, 0x60, 0x46, 0x24, 0x63, 0x46, 0x44, 0x65, 0x46, 0x74, 0x68, 0x46, 0x94, 0x6B, 0x46, 0xC4, 0x6D, 0x46, 0xF4, 0x70, + 0x47, 0x14, 0x73, 0x47, 0x44, 0x75, 0x47, 0x64, 0x78, 0x47, 0x94, 0x7A, 0x47, 0xC4, 0x7D, 0x47, 0xE4, 0x80, 0x48, 0x14, 0x82, 0x48, 0x34, 0x85, + 0x48, 0x64, 0x87, 0x48, 0x94, 0x8A, 0x48, 0xB4, 0x8D, 0x48, 0xE4, 0x8F, 0x49, 0x04, 0x92, 0x49, 0x34, 0x94, 0x49, 0x64, 0x97, 0x49, 0x84, 0x99, + 0x49, 0xB4, 0x9C, 0x49, 0xD4, 0x9F, 0x4A, 0x04, 0xA1, 0x4A, 0x24, 0xA4, 0x4A, 0x54, 0xA6, 0x4A, 0x74, 0xA9, 0x4A, 0xA4, 0xAB, 0x4A, 0xD4, 0xAE, + 0x4A, 0xF4, 0xB0, 0x4B, 0x24, 0xB3, 0x4B, 0x44, 0xB5, 0x4B, 0x74, 0xB8, 0x4B, 0x94, 0xBB, 0x4B, 0xC4, 0xBD, 0x4B, 0xE4, 0xC0, 0x4C, 0x14, 0xC2, + 0x4C, 0x34, 0xC5, 0x4C, 0x64, 0xC7, 0x4C, 0x84, 0xCA, 0x4C, 0xB4, 0xCC, 0x4C, 0xD4, 0xCF, 0x4D, 0x04, 0xD1, 0x4D, 0x24, 0xD4, 0x4D, 0x54, 0xD6, + 0x4D, 0x74, 0xD9, 0x4D, 0xA4, 0xDB, 0x4D, 0xC4, 0xDE, 0x4D, 0xF4, 0xE0, 0x4E, 0x14, 0xE3, 0x4E, 0x44, 0xE5, 0x4E, 0x64, 0xE8, 0x4E, 0x94, 0xEA, + 0x4E, 0xB4, 0xED, 0x4E, 0xE4, 0xEF, 0x4F, 0x04, 0xF2, 0x4F, 0x34, 0xF4, 0x4F, 0x54, 0xF6, 0x4F, 0x84, 0xF9, 0x4F, 0xA4, 0xFB, 0x4F, 0xD4, 0xFE, + 0x4F, 0xF5, 0x00, 0x50, 0x25, 0x03, 0x50, 0x45, 0x05, 0x50, 0x65, 0x08, 0x50, 0x95, 0x0A, 0x50, 0xB5, 0x0D, 0x50, 0xE5, 0x0F, 0x51, 0x05, 0x11, + 0x51, 0x35, 0x14, 0x51, 0x55, 0x16, 0x51, 0x75, 0x19, 0x51, 0xA5, 0x1B, 0x51, 0xC5, 0x1D, 0x51, 0xF5, 0x20, 0x52, 0x15, 0x22, 0x52, 0x45, 0x25, + 0x52, 0x65, 0x27, 0x52, 0x85, 0x2A, 0x52, 0xB5, 0x2C, 0x52, 0xD5, 0x2E, 0x53, 0x05, 0x31, 0x53, 0x25, 0x33, 0x53, 0x45, 0x35, 0x53, 0x75, 0x38, + 0x53, 0x95, 0x3A, 0x53, 0xB5, 0x3D, 0x53, 0xE5, 0x3F, 0x54, 0x05, 0x41, 0x54, 0x35, 0x44, 0x54, 0x55, 0x46, 0x54, 0x75, 0x48, 0x54, 0xA5, 0x4B, + 0x54, 0xC5, 0x4D, 0x54, 0xE5, 0x4F, 0x55, 0x15, 0x52, 0x55, 0x35, 0x54, 0x55, 0x55, 0x57, 0x55, 0x85, 0x59, 0x55, 0xA5, 0x5B, 0x55, 0xC5, 0x5E, + 0x55, 0xF5, 0x60, 0x56, 0x15, 0x62, 0x56, 0x35, 0x64, 0x56, 0x65, 0x67, 0x56, 0x85, 0x69, 0x56, 0xA5, 0x6B, 0x56, 0xD5, 0x6E, 0x56, 0xF5, 0x70, + 0x57, 0x15, 0x72, 0x57, 0x35, 0x75, 0x57, 0x65, 0x77, 0x57, 0x85, 0x79, 0x57, 0xA5, 0x7C, 0x57, 0xD5, 0x7E, 0x57, 0xF5, 0x80, 0x58, 0x15, 0x82, + 0x58, 0x35, 0x85, 0x58, 0x65, 0x87, 0x58, 0x85, 0x89, 0x58, 0xA5, 0x8B, 0x58, 0xD5, 0x8E, 0x58, 0xF5, 0x90, 0x59, 0x15, 0x92, 0x59, 0x35, 0x94, + 0x59, 0x65, 0x97, 0x59, 0x85, 0x99, 0x59, 0xA5, 0x9B, 0x59, 0xC5, 0x9D, 0x59, 0xF5, 0xA0, 0x5A, 0x15, 0xA2, 0x5A, 0x35, 0xA4, 0x5A, 0x55, 0xA6, + 0x5A, 0x75, 0xA9, 0x5A, 0xA5, 0xAB, 0x5A, 0xC5, 0xAD, 0x5A, 0xE5, 0xAF, 0x5B, 0x05, 0xB1, 0x5B, 0x35, 0xB4, 0x5B, 0x55, 0xB6, 0x5B, 0x75, 0xB8, + 0x5B, 0x95, 0xBA, 0x5B, 0xB5, 0xBC, 0x5B, 0xD5, 0xBF, 0x5C, 0x05, 0xC1, 0x5C, 0x25, 0xC3, 0x5C, 0x45, 0xC5, 0x5C, 0x65, 0xC7, 0x5C, 0x85, 0xC9, + 0x5C, 0xB5, 0xCC, 0x5C, 0xD5, 0xCE, 0x5C, 0xF5, 0xD0, 0x5D, 0x15, 0xD2, 0x5D, 0x35, 0xD4, 0x5D, 0x55, 0xD6, 0x5D, 0x75, 0xD9, 0x5D, 0xA5, 0xDB, + 0x5D, 0xC5, 0xDD, 0x5D, 0xE5, 0xDF, 0x5E, 0x05, 0xE1, 0x5E, 0x25, 0xE3, 0x5E, 0x45, 0xE5, 0x5E, 0x65, 0xE7, 0x5E, 0x95, 0xEA, 0x5E, 0xB5, 0xEC, + 0x5E, 0xD5, 0xEE, 0x5E, 0xF5, 0xF0, 0x5F, 0x15, 0xF2, 0x5F, 0x35, 0xF4, 0x5F, 0x55, 0xF6, 0x5F, 0x75, 0xF8, 0x5F, 0x95, 0xFA, 0x5F, 0xB5, 0xFC, + 0x5F, 0xD5, 0xFF, 0x60, 0x06, 0x01, 0x60, 0x26, 0x03, 0x60, 0x46, 0x05, 0x60, 0x66, 0x07, 0x60, 0x86, 0x09, 0x60, 0xA6, 0x0B, 0x60, 0xC6, 0x0D, + 0x60, 0xE6, 0x0F, 0x61, 0x06, 0x11, 0x61, 0x26, 0x13, 0x61, 0x46, 0x15, 0x61, 0x66, 0x17, 0x61, 0x86, 0x19, 0x61, 0xA6, 0x1B, 0x61, 0xC6, 0x1D, + 0x61, 0xE6, 0x1F, 0x62, 0x06, 0x21, 0x62, 0x26, 0x23, 0x62, 0x46, 0x25, 0x62, 0x66, 0x27, 0x62, 0x86, 0x29, 0x62, 0xA6, 0x2B, 0x62, 0xC6, 0x2D, + 0x62, 0xE6, 0x2F, 0x63, 0x06, 0x31, 0x63, 0x26, 0x33, 0x63, 0x46, 0x35, 0x63, 0x66, 0x37, 0x63, 0x86, 0x39, 0x63, 0xA6, 0x3B, 0x63, 0xC6, 0x3D, + 0x63, 0xE6, 0x3F, 0x64, 0x06, 0x41, 0x64, 0x26, 0x43, 0x64, 0x46, 0x45, 0x64, 0x66, 0x47, 0x64, 0x86, 0x49, 0x64, 0xA6, 0x4B, 0x64, 0xC6, 0x4D, + 0x64, 0xE6, 0x4F, 0x65, 0x06, 0x51, 0x65, 0x26, 0x53, 0x65, 0x46, 0x54, 0x65, 0x56, 0x56, 0x65, 0x76, 0x58, 0x65, 0x96, 0x5A, 0x65, 0xB6, 0x5C, + 0x65, 0xD6, 0x5E, 0x65, 0xF6, 0x60, 0x66, 0x16, 0x62, 0x66, 0x36, 0x64, 0x66, 0x56, 0x66, 0x66, 0x76, 0x67, 0x66, 0x86, 0x69, 0x66, 0xA6, 0x6B, + 0x66, 0xC6, 0x6D, 0x66, 0xE6, 0x6F, 0x67, 0x06, 0x71, 0x67, 0x26, 0x73, 0x67, 0x46, 0x75, 0x67, 0x56, 0x76, 0x67, 0x76, 0x78, 0x67, 0x96, 0x7A, + 0x67, 0xB6, 0x7C, 0x67, 0xD6, 0x7E, 0x67, 0xF6, 0x80, 0x68, 0x16, 0x81, 0x68, 0x26, 0x83, 0x68, 0x46, 0x85, 0x68, 0x66, 0x87, 0x68, 0x86, 0x89, + 0x68, 0xA6, 0x8A, 0x68, 0xB6, 0x8C, 0x68, 0xD6, 0x8E, 0x68, 0xF6, 0x90, 0x69, 0x16, 0x92, 0x69, 0x36, 0x93, 0x69, 0x46, 0x95, 0x69, 0x66, 0x97, + 0x69, 0x86, 0x99, 0x69, 0xA6, 0x9B, 0x69, 0xB6, 0x9C, 0x69, 0xD6, 0x9E, 0x69, 0xF6, 0xA0, 0x6A, 0x16, 0xA2, 0x6A, 0x36, 0xA3, 0x6A, 0x46, 0xA5, + 0x6A, 0x66, 0xA7, 0x6A, 0x86, 0xA9, 0x6A, 0x96, 0xAA, 0x6A, 0xB6, 0xAC, 0x6A, 0xD6, 0xAE, 0x6A, 0xF6, 0xB0, 0x6B, 0x06, 0xB1, 0x6B, 0x26, 0xB3, + 0x6B, 0x46, 0xB5, 0x6B, 0x66, 0xB6, 0x6B, 0x76, 0xB8, 0x6B, 0x96, 0xBA, 0x6B, 0xB6, 0xBC, 0x6B, 0xC6, 0xBD, 0x6B, 0xE6, 0xBF, 0x6C, 0x06, 0xC1, + 0x6C, 0x16, 0xC2, 0x6C, 0x36, 0xC4, 0x6C, 0x56, 0xC6, 0x6C, 0x66, 0xC7, 0x6C, 0x86, 0xC9, 0x6C, 0xA6, 0xCB, 0x6C, 0xB6, 0xCC, 0x6C, 0xD6, 0xCE, + 0x6C, 0xF6, 0xD0, 0x6D, 0x06, 0xD1, 0x6D, 0x26, 0xD3, 0x6D, 0x46, 0xD4, 0x6D, 0x56, 0xD6, 0x6D, 0x76, 0xD8, 0x6D, 0x96, 0xD9, 0x6D, 0xA6, 0xDB, + 0x6D, 0xC6, 0xDD, 0x6D, 0xD6, 0xDE, 0x6D, 0xF6, 0xE0, 0x6E, 0x16, 0xE1, 0x6E, 0x26, 0xE3, 0x6E, 0x46, 0xE5, 0x6E, 0x56, 0xE6, 0x6E, 0x76, 0xE8, + 0x6E, 0x96, 0xE9, 0x6E, 0xA6, 0xEB, 0x6E, 0xC6, 0xEC, 0x6E, 0xD6, 0xEE, 0x6E, 0xF6, 0xF0, 0x6F, 0x06, 0xF1, 0x6F, 0x26, 0xF3, 0x6F, 0x46, 0xF4, + 0x6F, 0x56, 0xF6, 0x6F, 0x76, 0xF7, 0x6F, 0x86, 0xF9, 0x6F, 0xA6, 0xFA, 0x6F, 0xB6, 0xFC, 0x6F, 0xD6, 0xFE, 0x6F, 0xE6, 0xFF, 0x70, 0x07, 0x01, + 0x70, 0x17, 0x02, 0x70, 0x37, 0x04, 0x70, 0x47, 0x05, 0x70, 0x67, 0x07, 0x70, 0x77, 0x08, 0x70, 0x97, 0x0A, 0x70, 0xA7, 0x0B, 0x70, 0xC7, 0x0D, + 0x70, 0xD7, 0x0E, 0x70, 0xF7, 0x10, 0x71, 0x07, 0x11, 0x71, 0x27, 0x12, 0x71, 0x37, 0x14, 0x71, 0x57, 0x15, 0x71, 0x67, 0x17, 0x71, 0x87, 0x18, + 0x71, 0x97, 0x1A, 0x71, 0xA7, 0x1B, 0x71, 0xC7, 0x1D, 0x71, 0xD7, 0x1E, 0x71, 0xF7, 0x1F, 0x72, 0x07, 0x21, 0x72, 0x27, 0x22, 0x72, 0x37, 0x24, + 0x72, 0x47, 0x25, 0x72, 0x67, 0x27, 0x72, 0x77, 0x28, 0x72, 0x97, 0x29, 0x72, 0xA7, 0x2B, 0x72, 0xB7, 0x2C, 0x72, 0xD7, 0x2E, 0x72, 0xE7, 0x2F, + 0x73, 0x07, 0x30, 0x73, 0x17, 0x32, 0x73, 0x27, 0x33, 0x73, 0x47, 0x34, 0x73, 0x57, 0x36, 0x73, 0x67, 0x37, 0x73, 0x87, 0x38, 0x73, 0x97, 0x3A, + 0x73, 0xA7, 0x3B, 0x73, 0xC7, 0x3C, 0x73, 0xD7, 0x3E, 0x73, 0xE7, 0x3F, 0x74, 0x07, 0x40, 0x74, 0x17, 0x42, 0x74, 0x27, 0x43, 0x74, 0x47, 0x44, + 0x74, 0x57, 0x46, 0x74, 0x67, 0x47, 0x74, 0x87, 0x48, 0x74, 0x97, 0x4A, 0x74, 0xA7, 0x4B, 0x74, 0xC7, 0x4C, 0x74, 0xD7, 0x4D, 0x74, 0xE7, 0x4F, + 0x74, 0xF7, 0x50, 0x75, 0x17, 0x51, 0x75, 0x27, 0x53, 0x75, 0x37, 0x54, 0x75, 0x47, 0x55, 0x75, 0x67, 0x56, 0x75, 0x77, 0x58, 0x75, 0x87, 0x59, + 0x75, 0x97, 0x5A, 0x75, 0xB7, 0x5B, 0x75, 0xC7, 0x5D, 0x75, 0xD7, 0x5E, 0x75, 0xE7, 0x5F, 0x76, 0x07, 0x60, 0x76, 0x17, 0x61, 0x76, 0x27, 0x63, + 0x76, 0x37, 0x64, 0x76, 0x47, 0x65, 0x76, 0x67, 0x66, 0x76, 0x77, 0x67, 0x76, 0x87, 0x69, 0x76, 0x97, 0x6A, 0x76, 0xA7, 0x6B, 0x76, 0xB7, 0x6C, + 0x76, 0xD7, 0x6D, 0x76, 0xE7, 0x6E, 0x76, 0xF7, 0x70, 0x77, 0x07, 0x71, 0x77, 0x17, 0x72, 0x77, 0x27, 0x73, 0x77, 0x47, 0x74, 0x77, 0x57, 0x75, + 0x77, 0x67, 0x76, 0x77, 0x77, 0x78, 0x77, 0x87, 0x79, 0x77, 0x97, 0x7A, 0x77, 0xA7, 0x7B, 0x77, 0xB7, 0x7C, 0x77, 0xD7, 0x7D, 0x77, 0xE7, 0x7E, + 0x77, 0xF7, 0x7F, 0x78, 0x07, 0x80, 0x78, 0x17, 0x81, 0x78, 0x27, 0x83, 0x78, 0x37, 0x84, 0x78, 0x47, 0x85, 0x78, 0x57, 0x86, 0x78, 0x67, 0x87, + 0x78, 0x77, 0x88, 0x78, 0x87, 0x89, 0x78, 0x97, 0x8A, 0x78, 0xA7, 0x8B, 0x78, 0xC7, 0x8C, 0x78, 0xD7, 0x8D, 0x78, 0xE7, 0x8E, 0x78, 0xF7, 0x8F, + 0x79, 0x07, 0x90, 0x79, 0x17, 0x91, 0x79, 0x27, 0x92, 0x79, 0x37, 0x93, 0x79, 0x47, 0x94, 0x79, 0x57, 0x95, 0x79, 0x67, 0x96, 0x79, 0x77, 0x97, + 0x79, 0x87, 0x98, 0x79, 0x97, 0x99, 0x79, 0xA7, 0x9A, 0x79, 0xB7, 0x9B, 0x79, 0xC7, 0x9C, 0x79, 0xD7, 0x9D, 0x79, 0xE7, 0x9E, 0x79, 0xE7, 0x9F, + 0x79, 0xF7, 0xA0, 0x7A, 0x07, 0xA1, 0x7A, 0x17, 0xA2, 0x7A, 0x27, 0xA3, 0x7A, 0x37, 0xA4, 0x7A, 0x47, 0xA5, 0x7A, 0x57, 0xA5, 0x7A, 0x67, 0xA6, + 0x7A, 0x77, 0xA7, 0x7A, 0x87, 0xA8, 0x7A, 0x97, 0xA9, 0x7A, 0xA7, 0xAA, 0x7A, 0xA7, 0xAB, 0x7A, 0xB7, 0xAC, 0x7A, 0xC7, 0xAD, 0x7A, 0xD7, 0xAE, + 0x7A, 0xE7, 0xAE, 0x7A, 0xF7, 0xAF, 0x7B, 0x07, 0xB0, 0x7B, 0x17, 0xB1, 0x7B, 0x17, 0xB2, 0x7B, 0x27, 0xB3, 0x7B, 0x37, 0xB4, 0x7B, 0x47, 0xB4, + 0x7B, 0x57, 0xB5, 0x7B, 0x67, 0xB6, 0x7B, 0x77, 0xB7, 0x7B, 0x77, 0xB8, 0x7B, 0x87, 0xB9, 0x7B, 0x97, 0xB9, 0x7B, 0xA7, 0xBA, 0x7B, 0xB7, 0xBB, + 0x7B, 0xB7, 0xBC, 0x7B, 0xC7, 0xBD, 0x7B, 0xD7, 0xBD, 0x7B, 0xE7, 0xBE, 0x7B, 0xF7, 0xBF, 0x7B, 0xF7, 0xC0, 0x7C, 0x07, 0xC1, 0x7C, 0x17, 0xC1, + 0x7C, 0x27, 0xC2, 0x7C, 0x27, 0xC3, 0x7C, 0x37, 0xC4, 0x7C, 0x47, 0xC4, 0x7C, 0x57, 0xC5, 0x7C, 0x57, 0xC6, 0x7C, 0x67, 0xC7, 0x7C, 0x77, 0xC7, + 0x7C, 0x87, 0xC8, 0x7C, 0x87, 0xC9, 0x7C, 0x97, 0xC9, 0x7C, 0xA7, 0xCA, 0x7C, 0xA7, 0xCB, 0x7C, 0xB7, 0xCC, 0x7C, 0xC7, 0xCC, 0x7C, 0xD7, 0xCD, + 0x7C, 0xD7, 0xCE, 0x7C, 0xE7, 0xCE, 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x17, 0xD1, 0x7D, 0x17, 0xD2, 0x7D, 0x27, 0xD2, + 0x7D, 0x37, 0xD3, 0x7D, 0x37, 0xD4, 0x7D, 0x47, 0xD4, 0x7D, 0x57, 0xD5, 0x7D, 0x57, 0xD5, 0x7D, 0x67, 0xD6, 0x7D, 0x67, 0xD7, 0x7D, 0x77, 0xD7, + 0x7D, 0x87, 0xD8, 0x7D, 0x87, 0xD9, 0x7D, 0x97, 0xD9, 0x7D, 0x97, 0xDA, 0x7D, 0xA7, 0xDA, 0x7D, 0xB7, 0xDB, 0x7D, 0xB7, 0xDC, 0x7D, 0xC7, 0xDC, + 0x7D, 0xC7, 0xDD, 0x7D, 0xD7, 0xDD, 0x7D, 0xE7, 0xDE, 0x7D, 0xE7, 0xDE, 0x7D, 0xF7, 0xDF, 0x7D, 0xF7, 0xE0, 0x7E, 0x07, 0xE0, 0x7E, 0x07, 0xE1, + 0x7E, 0x17, 0xE1, 0x7E, 0x17, 0xE2, 0x7E, 0x27, 0xE2, 0x7E, 0x27, 0xE3, 0x7E, 0x37, 0xE3, 0x7E, 0x37, 0xE4, 0x7E, 0x47, 0xE4, 0x7E, 0x57, 0xE5, + 0x7E, 0x57, 0xE5, 0x7E, 0x67, 0xE6, 0x7E, 0x67, 0xE6, 0x7E, 0x67, 0xE7, 0x7E, 0x77, 0xE7, 0x7E, 0x77, 0xE8, 0x7E, 0x87, 0xE8, 0x7E, 0x87, 0xE9, + 0x7E, 0x97, 0xE9, 0x7E, 0x97, 0xEA, 0x7E, 0xA7, 0xEA, 0x7E, 0xA7, 0xEA, 0x7E, 0xB7, 0xEB, 0x7E, 0xB7, 0xEB, 0x7E, 0xC7, 0xEC, 0x7E, 0xC7, 0xEC, + 0x7E, 0xC7, 0xED, 0x7E, 0xD7, 0xED, 0x7E, 0xD7, 0xED, 0x7E, 0xE7, 0xEE, 0x7E, 0xE7, 0xEE, 0x7E, 0xE7, 0xEF, 0x7E, 0xF7, 0xEF, 0x7E, 0xF7, 0xEF, + 0x7F, 0x07, 0xF0, 0x7F, 0x07, 0xF0, 0x7F, 0x07, 0xF1, 0x7F, 0x17, 0xF1, 0x7F, 0x17, 0xF1, 0x7F, 0x17, 0xF2, 0x7F, 0x27, 0xF2, 0x7F, 0x27, 0xF2, + 0x7F, 0x37, 0xF3, 0x7F, 0x37, 0xF3, 0x7F, 0x37, 0xF3, 0x7F, 0x47, 0xF4, 0x7F, 0x47, 0xF4, 0x7F, 0x47, 0xF4, 0x7F, 0x57, 0xF5, 0x7F, 0x57, 0xF5, + 0x7F, 0x57, 0xF5, 0x7F, 0x57, 0xF6, 0x7F, 0x67, 0xF6, 0x7F, 0x67, 0xF6, 0x7F, 0x67, 0xF6, 0x7F, 0x77, 0xF7, 0x7F, 0x77, 0xF7, 0x7F, 0x77, 0xF7, + 0x7F, 0x77, 0xF8, 0x7F, 0x87, 0xF8, 0x7F, 0x87, 0xF8, 0x7F, 0x87, 0xF8, 0x7F, 0x87, 0xF9, 0x7F, 0x97, 0xF9, 0x7F, 0x97, 0xF9, 0x7F, 0x97, 0xF9, + 0x7F, 0x97, 0xFA, 0x7F, 0xA7, 0xFA, 0x7F, 0xA7, 0xFA, 0x7F, 0xA7, 0xFA, 0x7F, 0xA7, 0xFA, 0x7F, 0xB7, 0xFB, 0x7F, 0xB7, 0xFB, 0x7F, 0xB7, 0xFB, + 0x7F, 0xB7, 0xFB, 0x7F, 0xB7, 0xFB, 0x7F, 0xC7, 0xFC, 0x7F, 0xC7, 0xFC, 0x7F, 0xC7, 0xFC, 0x7F, 0xC7, 0xFC, 0x7F, 0xC7, 0xFC, 0x7F, 0xC7, 0xFC, + 0x7F, 0xD7, 0xFD, 0x7F, 0xD7, 0xFD, 0x7F, 0xD7, 0xFD, 0x7F, 0xD7, 0xFD, 0x7F, 0xD7, 0xFD, 0x7F, 0xD7, 0xFD, 0x7F, 0xD7, 0xFD, 0x7F, 0xD7, 0xFE, + 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, + 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, + 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xF7, 0xFF, +}; +#else +// range 2000 +const uint8_t LUT_sine_8192_quad_packed[3072] = { + 0x00, 0x00, 0x02, 0x00, 0x30, 0x05, 0x00, 0x60, 0x08, 0x00, 0x90, 0x0B, 0x00, 0xC0, 0x0E, 0x00, 0xF0, 0x11, 0x01, 0x20, 0x14, 0x01, 0x50, 0x17, + 0x01, 0x90, 0x1A, 0x01, 0xC0, 0x1D, 0x01, 0xF0, 0x20, 0x02, 0x20, 0x23, 0x02, 0x50, 0x26, 0x02, 0x80, 0x29, 0x02, 0xB0, 0x2C, 0x02, 0xE0, 0x30, + 0x03, 0x10, 0x33, 0x03, 0x40, 0x36, 0x03, 0x70, 0x39, 0x03, 0xA0, 0x3C, 0x03, 0xD0, 0x3F, 0x04, 0x00, 0x42, 0x04, 0x30, 0x45, 0x04, 0x70, 0x48, + 0x04, 0xA0, 0x4B, 0x04, 0xD0, 0x4E, 0x05, 0x00, 0x51, 0x05, 0x30, 0x54, 0x05, 0x60, 0x57, 0x05, 0x90, 0x5A, 0x05, 0xC0, 0x5E, 0x05, 0xF0, 0x61, + 0x06, 0x20, 0x64, 0x06, 0x50, 0x67, 0x06, 0x80, 0x6A, 0x06, 0xB0, 0x6D, 0x06, 0xE0, 0x70, 0x07, 0x10, 0x73, 0x07, 0x50, 0x76, 0x07, 0x80, 0x79, + 0x07, 0xB0, 0x7C, 0x07, 0xE0, 0x7F, 0x08, 0x10, 0x82, 0x08, 0x40, 0x85, 0x08, 0x70, 0x88, 0x08, 0xA0, 0x8B, 0x08, 0xD0, 0x8F, 0x09, 0x00, 0x92, + 0x09, 0x30, 0x95, 0x09, 0x60, 0x98, 0x09, 0x90, 0x9B, 0x09, 0xC0, 0x9E, 0x09, 0xF0, 0xA1, 0x0A, 0x20, 0xA4, 0x0A, 0x50, 0xA7, 0x0A, 0x90, 0xAA, + 0x0A, 0xC0, 0xAD, 0x0A, 0xF0, 0xB0, 0x0B, 0x20, 0xB3, 0x0B, 0x50, 0xB6, 0x0B, 0x80, 0xB9, 0x0B, 0xB0, 0xBC, 0x0B, 0xE0, 0xBF, 0x0C, 0x10, 0xC3, + 0x0C, 0x40, 0xC6, 0x0C, 0x70, 0xC9, 0x0C, 0xA0, 0xCC, 0x0C, 0xD0, 0xCF, 0x0D, 0x00, 0xD2, 0x0D, 0x30, 0xD5, 0x0D, 0x60, 0xD8, 0x0D, 0x90, 0xDB, + 0x0D, 0xC0, 0xDE, 0x0D, 0xF0, 0xE1, 0x0E, 0x30, 0xE4, 0x0E, 0x60, 0xE7, 0x0E, 0x90, 0xEA, 0x0E, 0xC0, 0xED, 0x0E, 0xF0, 0xF0, 0x0F, 0x20, 0xF3, + 0x0F, 0x50, 0xF6, 0x0F, 0x80, 0xF9, 0x0F, 0xB0, 0xFC, 0x0F, 0xE0, 0xFF, 0x10, 0x11, 0x03, 0x10, 0x41, 0x06, 0x10, 0x71, 0x09, 0x10, 0xA1, 0x0C, + 0x10, 0xD1, 0x0F, 0x11, 0x01, 0x12, 0x11, 0x31, 0x15, 0x11, 0x61, 0x18, 0x11, 0x91, 0x1B, 0x11, 0xC1, 0x1E, 0x11, 0xF1, 0x21, 0x12, 0x21, 0x24, + 0x12, 0x51, 0x27, 0x12, 0x81, 0x2A, 0x12, 0xC1, 0x2D, 0x12, 0xF1, 0x30, 0x13, 0x21, 0x33, 0x13, 0x51, 0x36, 0x13, 0x81, 0x39, 0x13, 0xB1, 0x3C, + 0x13, 0xE1, 0x3F, 0x14, 0x11, 0x42, 0x14, 0x41, 0x45, 0x14, 0x71, 0x48, 0x14, 0xA1, 0x4B, 0x14, 0xD1, 0x4E, 0x15, 0x01, 0x51, 0x15, 0x31, 0x54, + 0x15, 0x61, 0x57, 0x15, 0x91, 0x5A, 0x15, 0xC1, 0x5D, 0x15, 0xF1, 0x60, 0x16, 0x21, 0x64, 0x16, 0x51, 0x67, 0x16, 0x81, 0x6A, 0x16, 0xB1, 0x6D, + 0x16, 0xE1, 0x70, 0x17, 0x11, 0x73, 0x17, 0x41, 0x76, 0x17, 0x71, 0x79, 0x17, 0xA1, 0x7C, 0x17, 0xD1, 0x7F, 0x18, 0x01, 0x82, 0x18, 0x31, 0x85, + 0x18, 0x61, 0x88, 0x18, 0x91, 0x8B, 0x18, 0xC1, 0x8E, 0x18, 0xF1, 0x91, 0x19, 0x21, 0x94, 0x19, 0x51, 0x97, 0x19, 0x81, 0x9A, 0x19, 0xB1, 0x9D, + 0x19, 0xE1, 0xA0, 0x1A, 0x11, 0xA3, 0x1A, 0x41, 0xA6, 0x1A, 0x71, 0xA9, 0x1A, 0xA1, 0xAC, 0x1A, 0xD1, 0xAF, 0x1B, 0x01, 0xB2, 0x1B, 0x31, 0xB5, + 0x1B, 0x61, 0xB8, 0x1B, 0x91, 0xBB, 0x1B, 0xC1, 0xBE, 0x1B, 0xF1, 0xC1, 0x1C, 0x21, 0xC4, 0x1C, 0x51, 0xC7, 0x1C, 0x81, 0xCA, 0x1C, 0xB1, 0xCD, + 0x1C, 0xE1, 0xD0, 0x1D, 0x11, 0xD3, 0x1D, 0x41, 0xD6, 0x1D, 0x71, 0xD9, 0x1D, 0xA1, 0xDC, 0x1D, 0xD1, 0xDF, 0x1E, 0x01, 0xE1, 0x1E, 0x31, 0xE4, + 0x1E, 0x61, 0xE7, 0x1E, 0x91, 0xEA, 0x1E, 0xC1, 0xED, 0x1E, 0xF1, 0xF0, 0x1F, 0x21, 0xF3, 0x1F, 0x51, 0xF6, 0x1F, 0x81, 0xF9, 0x1F, 0xB1, 0xFC, + 0x1F, 0xE1, 0xFF, 0x20, 0x12, 0x02, 0x20, 0x42, 0x05, 0x20, 0x72, 0x08, 0x20, 0xA2, 0x0B, 0x20, 0xD2, 0x0E, 0x21, 0x02, 0x11, 0x21, 0x22, 0x14, + 0x21, 0x52, 0x17, 0x21, 0x82, 0x1A, 0x21, 0xB2, 0x1D, 0x21, 0xE2, 0x20, 0x22, 0x12, 0x23, 0x22, 0x42, 0x26, 0x22, 0x72, 0x29, 0x22, 0xA2, 0x2C, + 0x22, 0xD2, 0x2F, 0x23, 0x02, 0x31, 0x23, 0x32, 0x34, 0x23, 0x62, 0x37, 0x23, 0x92, 0x3A, 0x23, 0xC2, 0x3D, 0x23, 0xF2, 0x40, 0x24, 0x22, 0x43, + 0x24, 0x52, 0x46, 0x24, 0x82, 0x49, 0x24, 0xA2, 0x4C, 0x24, 0xD2, 0x4F, 0x25, 0x02, 0x52, 0x25, 0x32, 0x55, 0x25, 0x62, 0x58, 0x25, 0x92, 0x5B, + 0x25, 0xC2, 0x5D, 0x25, 0xF2, 0x60, 0x26, 0x22, 0x63, 0x26, 0x52, 0x66, 0x26, 0x82, 0x69, 0x26, 0xB2, 0x6C, 0x26, 0xE2, 0x6F, 0x27, 0x02, 0x72, + 0x27, 0x32, 0x75, 0x27, 0x62, 0x78, 0x27, 0x92, 0x7B, 0x27, 0xC2, 0x7E, 0x27, 0xF2, 0x80, 0x28, 0x22, 0x83, 0x28, 0x52, 0x86, 0x28, 0x82, 0x89, + 0x28, 0xB2, 0x8C, 0x28, 0xE2, 0x8F, 0x29, 0x02, 0x92, 0x29, 0x32, 0x95, 0x29, 0x62, 0x98, 0x29, 0x92, 0x9B, 0x29, 0xC2, 0x9D, 0x29, 0xF2, 0xA0, + 0x2A, 0x22, 0xA3, 0x2A, 0x52, 0xA6, 0x2A, 0x82, 0xA9, 0x2A, 0xA2, 0xAC, 0x2A, 0xD2, 0xAF, 0x2B, 0x02, 0xB2, 0x2B, 0x32, 0xB5, 0x2B, 0x62, 0xB7, + 0x2B, 0x92, 0xBA, 0x2B, 0xC2, 0xBD, 0x2B, 0xF2, 0xC0, 0x2C, 0x12, 0xC3, 0x2C, 0x42, 0xC6, 0x2C, 0x72, 0xC9, 0x2C, 0xA2, 0xCB, 0x2C, 0xD2, 0xCE, + 0x2D, 0x02, 0xD1, 0x2D, 0x32, 0xD4, 0x2D, 0x62, 0xD7, 0x2D, 0x82, 0xDA, 0x2D, 0xB2, 0xDD, 0x2D, 0xE2, 0xE0, 0x2E, 0x12, 0xE2, 0x2E, 0x42, 0xE5, + 0x2E, 0x72, 0xE8, 0x2E, 0x92, 0xEB, 0x2E, 0xC2, 0xEE, 0x2E, 0xF2, 0xF1, 0x2F, 0x22, 0xF3, 0x2F, 0x52, 0xF6, 0x2F, 0x82, 0xF9, 0x2F, 0xB2, 0xFC, + 0x2F, 0xD2, 0xFF, 0x30, 0x03, 0x02, 0x30, 0x33, 0x04, 0x30, 0x63, 0x07, 0x30, 0x93, 0x0A, 0x30, 0xC3, 0x0D, 0x30, 0xE3, 0x10, 0x31, 0x13, 0x13, + 0x31, 0x43, 0x15, 0x31, 0x73, 0x18, 0x31, 0xA3, 0x1B, 0x31, 0xC3, 0x1E, 0x31, 0xF3, 0x21, 0x32, 0x23, 0x23, 0x32, 0x53, 0x26, 0x32, 0x83, 0x29, + 0x32, 0xA3, 0x2C, 0x32, 0xD3, 0x2F, 0x33, 0x03, 0x31, 0x33, 0x33, 0x34, 0x33, 0x63, 0x37, 0x33, 0x83, 0x3A, 0x33, 0xB3, 0x3D, 0x33, 0xE3, 0x3F, + 0x34, 0x13, 0x42, 0x34, 0x43, 0x45, 0x34, 0x63, 0x48, 0x34, 0x93, 0x4B, 0x34, 0xC3, 0x4D, 0x34, 0xF3, 0x50, 0x35, 0x23, 0x53, 0x35, 0x43, 0x56, + 0x35, 0x73, 0x58, 0x35, 0xA3, 0x5B, 0x35, 0xD3, 0x5E, 0x35, 0xF3, 0x61, 0x36, 0x23, 0x64, 0x36, 0x53, 0x66, 0x36, 0x83, 0x69, 0x36, 0xA3, 0x6C, + 0x36, 0xD3, 0x6F, 0x37, 0x03, 0x71, 0x37, 0x33, 0x74, 0x37, 0x53, 0x77, 0x37, 0x83, 0x7A, 0x37, 0xB3, 0x7C, 0x37, 0xE3, 0x7F, 0x38, 0x03, 0x82, + 0x38, 0x33, 0x85, 0x38, 0x63, 0x87, 0x38, 0x93, 0x8A, 0x38, 0xB3, 0x8D, 0x38, 0xE3, 0x90, 0x39, 0x13, 0x92, 0x39, 0x43, 0x95, 0x39, 0x63, 0x98, + 0x39, 0x93, 0x9A, 0x39, 0xC3, 0x9D, 0x39, 0xF3, 0xA0, 0x3A, 0x13, 0xA3, 0x3A, 0x43, 0xA5, 0x3A, 0x73, 0xA8, 0x3A, 0x93, 0xAB, 0x3A, 0xC3, 0xAD, + 0x3A, 0xF3, 0xB0, 0x3B, 0x13, 0xB3, 0x3B, 0x43, 0xB6, 0x3B, 0x73, 0xB8, 0x3B, 0xA3, 0xBB, 0x3B, 0xC3, 0xBE, 0x3B, 0xF3, 0xC0, 0x3C, 0x23, 0xC3, + 0x3C, 0x43, 0xC6, 0x3C, 0x73, 0xC8, 0x3C, 0xA3, 0xCB, 0x3C, 0xC3, 0xCE, 0x3C, 0xF3, 0xD0, 0x3D, 0x23, 0xD3, 0x3D, 0x43, 0xD6, 0x3D, 0x73, 0xD8, + 0x3D, 0xA3, 0xDB, 0x3D, 0xC3, 0xDE, 0x3D, 0xF3, 0xE0, 0x3E, 0x23, 0xE3, 0x3E, 0x43, 0xE6, 0x3E, 0x73, 0xE8, 0x3E, 0xA3, 0xEB, 0x3E, 0xC3, 0xEE, + 0x3E, 0xF3, 0xF0, 0x3F, 0x23, 0xF3, 0x3F, 0x43, 0xF6, 0x3F, 0x73, 0xF8, 0x3F, 0xA3, 0xFB, 0x3F, 0xC3, 0xFE, 0x3F, 0xF4, 0x00, 0x40, 0x24, 0x03, + 0x40, 0x44, 0x06, 0x40, 0x74, 0x08, 0x40, 0x94, 0x0B, 0x40, 0xC4, 0x0D, 0x40, 0xF4, 0x10, 0x41, 0x14, 0x13, 0x41, 0x44, 0x15, 0x41, 0x74, 0x18, + 0x41, 0x94, 0x1A, 0x41, 0xC4, 0x1D, 0x41, 0xE4, 0x20, 0x42, 0x14, 0x22, 0x42, 0x44, 0x25, 0x42, 0x64, 0x28, 0x42, 0x94, 0x2A, 0x42, 0xB4, 0x2D, + 0x42, 0xE4, 0x2F, 0x43, 0x14, 0x32, 0x43, 0x34, 0x34, 0x43, 0x64, 0x37, 0x43, 0x84, 0x3A, 0x43, 0xB4, 0x3C, 0x43, 0xE4, 0x3F, 0x44, 0x04, 0x41, + 0x44, 0x34, 0x44, 0x44, 0x54, 0x47, 0x44, 0x84, 0x49, 0x44, 0xA4, 0x4C, 0x44, 0xD4, 0x4E, 0x44, 0xF4, 0x51, 0x45, 0x24, 0x53, 0x45, 0x54, 0x56, + 0x45, 0x74, 0x58, 0x45, 0xA4, 0x5B, 0x45, 0xC4, 0x5E, 0x45, 0xF4, 0x60, 0x46, 0x14, 0x63, 0x46, 0x44, 0x65, 0x46, 0x64, 0x68, 0x46, 0x94, 0x6A, + 0x46, 0xB4, 0x6D, 0x46, 0xE4, 0x6F, 0x47, 0x14, 0x72, 0x47, 0x34, 0x74, 0x47, 0x64, 0x77, 0x47, 0x84, 0x79, 0x47, 0xB4, 0x7C, 0x47, 0xD4, 0x7E, + 0x48, 0x04, 0x81, 0x48, 0x24, 0x83, 0x48, 0x54, 0x86, 0x48, 0x74, 0x88, 0x48, 0xA4, 0x8B, 0x48, 0xC4, 0x8D, 0x48, 0xF4, 0x90, 0x49, 0x14, 0x92, + 0x49, 0x44, 0x95, 0x49, 0x64, 0x97, 0x49, 0x94, 0x9A, 0x49, 0xB4, 0x9C, 0x49, 0xE4, 0x9F, 0x4A, 0x04, 0xA1, 0x4A, 0x24, 0xA4, 0x4A, 0x54, 0xA6, + 0x4A, 0x74, 0xA9, 0x4A, 0xA4, 0xAB, 0x4A, 0xC4, 0xAE, 0x4A, 0xF4, 0xB0, 0x4B, 0x14, 0xB2, 0x4B, 0x44, 0xB5, 0x4B, 0x64, 0xB7, 0x4B, 0x94, 0xBA, + 0x4B, 0xB4, 0xBC, 0x4B, 0xD4, 0xBF, 0x4C, 0x04, 0xC1, 0x4C, 0x24, 0xC4, 0x4C, 0x54, 0xC6, 0x4C, 0x74, 0xC8, 0x4C, 0xA4, 0xCB, 0x4C, 0xC4, 0xCD, + 0x4C, 0xE4, 0xD0, 0x4D, 0x14, 0xD2, 0x4D, 0x34, 0xD5, 0x4D, 0x64, 0xD7, 0x4D, 0x84, 0xD9, 0x4D, 0xB4, 0xDC, 0x4D, 0xD4, 0xDE, 0x4D, 0xF4, 0xE1, + 0x4E, 0x24, 0xE3, 0x4E, 0x44, 0xE5, 0x4E, 0x74, 0xE8, 0x4E, 0x94, 0xEA, 0x4E, 0xB4, 0xEC, 0x4E, 0xE4, 0xEF, 0x4F, 0x04, 0xF1, 0x4F, 0x24, 0xF4, + 0x4F, 0x54, 0xF6, 0x4F, 0x74, 0xF8, 0x4F, 0xA4, 0xFB, 0x4F, 0xC4, 0xFD, 0x4F, 0xE4, 0xFF, 0x50, 0x15, 0x02, 0x50, 0x35, 0x04, 0x50, 0x55, 0x06, + 0x50, 0x85, 0x09, 0x50, 0xA5, 0x0B, 0x50, 0xC5, 0x0E, 0x50, 0xF5, 0x10, 0x51, 0x15, 0x12, 0x51, 0x35, 0x15, 0x51, 0x65, 0x17, 0x51, 0x85, 0x19, + 0x51, 0xA5, 0x1C, 0x51, 0xD5, 0x1E, 0x51, 0xF5, 0x20, 0x52, 0x15, 0x22, 0x52, 0x45, 0x25, 0x52, 0x65, 0x27, 0x52, 0x85, 0x29, 0x52, 0xB5, 0x2C, + 0x52, 0xD5, 0x2E, 0x52, 0xF5, 0x30, 0x53, 0x15, 0x33, 0x53, 0x45, 0x35, 0x53, 0x65, 0x37, 0x53, 0x85, 0x39, 0x53, 0xB5, 0x3C, 0x53, 0xD5, 0x3E, + 0x53, 0xF5, 0x40, 0x54, 0x15, 0x43, 0x54, 0x45, 0x45, 0x54, 0x65, 0x47, 0x54, 0x85, 0x49, 0x54, 0xA5, 0x4C, 0x54, 0xD5, 0x4E, 0x54, 0xF5, 0x50, + 0x55, 0x15, 0x52, 0x55, 0x35, 0x55, 0x55, 0x65, 0x57, 0x55, 0x85, 0x59, 0x55, 0xA5, 0x5B, 0x55, 0xC5, 0x5E, 0x55, 0xF5, 0x60, 0x56, 0x15, 0x62, + 0x56, 0x35, 0x64, 0x56, 0x55, 0x66, 0x56, 0x85, 0x69, 0x56, 0xA5, 0x6B, 0x56, 0xC5, 0x6D, 0x56, 0xE5, 0x6F, 0x57, 0x05, 0x71, 0x57, 0x35, 0x74, + 0x57, 0x55, 0x76, 0x57, 0x75, 0x78, 0x57, 0x95, 0x7A, 0x57, 0xB5, 0x7C, 0x57, 0xE5, 0x7F, 0x58, 0x05, 0x81, 0x58, 0x25, 0x83, 0x58, 0x45, 0x85, + 0x58, 0x65, 0x87, 0x58, 0x85, 0x89, 0x58, 0xB5, 0x8C, 0x58, 0xD5, 0x8E, 0x58, 0xF5, 0x90, 0x59, 0x15, 0x92, 0x59, 0x35, 0x94, 0x59, 0x55, 0x96, + 0x59, 0x75, 0x99, 0x59, 0xA5, 0x9B, 0x59, 0xC5, 0x9D, 0x59, 0xE5, 0x9F, 0x5A, 0x05, 0xA1, 0x5A, 0x25, 0xA3, 0x5A, 0x45, 0xA5, 0x5A, 0x65, 0xA7, + 0x5A, 0x85, 0xAA, 0x5A, 0xB5, 0xAC, 0x5A, 0xD5, 0xAE, 0x5A, 0xF5, 0xB0, 0x5B, 0x15, 0xB2, 0x5B, 0x35, 0xB4, 0x5B, 0x55, 0xB6, 0x5B, 0x75, 0xB8, + 0x5B, 0x95, 0xBA, 0x5B, 0xB5, 0xBC, 0x5B, 0xD5, 0xBF, 0x5C, 0x05, 0xC1, 0x5C, 0x25, 0xC3, 0x5C, 0x45, 0xC5, 0x5C, 0x65, 0xC7, 0x5C, 0x85, 0xC9, + 0x5C, 0xA5, 0xCB, 0x5C, 0xC5, 0xCD, 0x5C, 0xE5, 0xCF, 0x5D, 0x05, 0xD1, 0x5D, 0x25, 0xD3, 0x5D, 0x45, 0xD5, 0x5D, 0x65, 0xD7, 0x5D, 0x85, 0xD9, + 0x5D, 0xA5, 0xDB, 0x5D, 0xC5, 0xDD, 0x5D, 0xE5, 0xDF, 0x5E, 0x05, 0xE1, 0x5E, 0x25, 0xE3, 0x5E, 0x45, 0xE5, 0x5E, 0x65, 0xE7, 0x5E, 0x85, 0xE9, + 0x5E, 0xA5, 0xEB, 0x5E, 0xC5, 0xED, 0x5E, 0xE5, 0xEF, 0x5F, 0x05, 0xF1, 0x5F, 0x25, 0xF3, 0x5F, 0x45, 0xF5, 0x5F, 0x65, 0xF7, 0x5F, 0x85, 0xF9, + 0x5F, 0xA5, 0xFB, 0x5F, 0xC5, 0xFD, 0x5F, 0xE5, 0xFF, 0x60, 0x06, 0x01, 0x60, 0x26, 0x03, 0x60, 0x46, 0x05, 0x60, 0x66, 0x07, 0x60, 0x86, 0x09, + 0x60, 0xA6, 0x0B, 0x60, 0xC6, 0x0D, 0x60, 0xE6, 0x0F, 0x61, 0x06, 0x11, 0x61, 0x26, 0x13, 0x61, 0x46, 0x15, 0x61, 0x66, 0x17, 0x61, 0x86, 0x19, + 0x61, 0x96, 0x1A, 0x61, 0xB6, 0x1C, 0x61, 0xD6, 0x1E, 0x61, 0xF6, 0x20, 0x62, 0x16, 0x22, 0x62, 0x36, 0x24, 0x62, 0x56, 0x26, 0x62, 0x76, 0x28, + 0x62, 0x96, 0x2A, 0x62, 0xB6, 0x2C, 0x62, 0xC6, 0x2D, 0x62, 0xE6, 0x2F, 0x63, 0x06, 0x31, 0x63, 0x26, 0x33, 0x63, 0x46, 0x35, 0x63, 0x66, 0x37, + 0x63, 0x86, 0x39, 0x63, 0xA6, 0x3A, 0x63, 0xB6, 0x3C, 0x63, 0xD6, 0x3E, 0x63, 0xF6, 0x40, 0x64, 0x16, 0x42, 0x64, 0x36, 0x44, 0x64, 0x56, 0x46, + 0x64, 0x66, 0x47, 0x64, 0x86, 0x49, 0x64, 0xA6, 0x4B, 0x64, 0xC6, 0x4D, 0x64, 0xE6, 0x4F, 0x65, 0x06, 0x50, 0x65, 0x16, 0x52, 0x65, 0x36, 0x54, + 0x65, 0x56, 0x56, 0x65, 0x76, 0x58, 0x65, 0x96, 0x59, 0x65, 0xA6, 0x5B, 0x65, 0xC6, 0x5D, 0x65, 0xE6, 0x5F, 0x66, 0x06, 0x61, 0x66, 0x16, 0x62, + 0x66, 0x36, 0x64, 0x66, 0x56, 0x66, 0x66, 0x76, 0x68, 0x66, 0x86, 0x69, 0x66, 0xA6, 0x6B, 0x66, 0xC6, 0x6D, 0x66, 0xE6, 0x6F, 0x66, 0xF6, 0x70, + 0x67, 0x16, 0x72, 0x67, 0x36, 0x74, 0x67, 0x56, 0x76, 0x67, 0x66, 0x77, 0x67, 0x86, 0x79, 0x67, 0xA6, 0x7B, 0x67, 0xC6, 0x7C, 0x67, 0xD6, 0x7E, + 0x67, 0xF6, 0x80, 0x68, 0x16, 0x81, 0x68, 0x26, 0x83, 0x68, 0x46, 0x85, 0x68, 0x66, 0x87, 0x68, 0x76, 0x88, 0x68, 0x96, 0x8A, 0x68, 0xB6, 0x8C, + 0x68, 0xC6, 0x8D, 0x68, 0xE6, 0x8F, 0x69, 0x06, 0x91, 0x69, 0x16, 0x92, 0x69, 0x36, 0x94, 0x69, 0x56, 0x96, 0x69, 0x66, 0x97, 0x69, 0x86, 0x99, + 0x69, 0xA6, 0x9B, 0x69, 0xB6, 0x9C, 0x69, 0xD6, 0x9E, 0x69, 0xF6, 0x9F, 0x6A, 0x06, 0xA1, 0x6A, 0x26, 0xA3, 0x6A, 0x36, 0xA4, 0x6A, 0x56, 0xA6, + 0x6A, 0x76, 0xA8, 0x6A, 0x86, 0xA9, 0x6A, 0xA6, 0xAB, 0x6A, 0xC6, 0xAC, 0x6A, 0xD6, 0xAE, 0x6A, 0xF6, 0xB0, 0x6B, 0x06, 0xB1, 0x6B, 0x26, 0xB3, + 0x6B, 0x36, 0xB4, 0x6B, 0x56, 0xB6, 0x6B, 0x76, 0xB7, 0x6B, 0x86, 0xB9, 0x6B, 0xA6, 0xBB, 0x6B, 0xB6, 0xBC, 0x6B, 0xD6, 0xBE, 0x6B, 0xE6, 0xBF, + 0x6C, 0x06, 0xC1, 0x6C, 0x16, 0xC2, 0x6C, 0x36, 0xC4, 0x6C, 0x56, 0xC5, 0x6C, 0x66, 0xC7, 0x6C, 0x86, 0xC8, 0x6C, 0x96, 0xCA, 0x6C, 0xB6, 0xCB, + 0x6C, 0xC6, 0xCD, 0x6C, 0xE6, 0xCE, 0x6C, 0xF6, 0xD0, 0x6D, 0x16, 0xD1, 0x6D, 0x26, 0xD3, 0x6D, 0x46, 0xD4, 0x6D, 0x56, 0xD6, 0x6D, 0x76, 0xD7, + 0x6D, 0x86, 0xD9, 0x6D, 0xA6, 0xDA, 0x6D, 0xB6, 0xDC, 0x6D, 0xD6, 0xDD, 0x6D, 0xE6, 0xDF, 0x6D, 0xF6, 0xE0, 0x6E, 0x16, 0xE2, 0x6E, 0x26, 0xE3, + 0x6E, 0x46, 0xE5, 0x6E, 0x56, 0xE6, 0x6E, 0x76, 0xE7, 0x6E, 0x86, 0xE9, 0x6E, 0xA6, 0xEA, 0x6E, 0xB6, 0xEC, 0x6E, 0xC6, 0xED, 0x6E, 0xE6, 0xEF, + 0x6E, 0xF6, 0xF0, 0x6F, 0x16, 0xF1, 0x6F, 0x26, 0xF3, 0x6F, 0x36, 0xF4, 0x6F, 0x56, 0xF6, 0x6F, 0x66, 0xF7, 0x6F, 0x86, 0xF8, 0x6F, 0x96, 0xFA, + 0x6F, 0xA6, 0xFB, 0x6F, 0xC6, 0xFD, 0x6F, 0xD6, 0xFE, 0x6F, 0xF6, 0xFF, 0x70, 0x07, 0x01, 0x70, 0x17, 0x02, 0x70, 0x37, 0x03, 0x70, 0x47, 0x05, + 0x70, 0x57, 0x06, 0x70, 0x77, 0x07, 0x70, 0x87, 0x09, 0x70, 0x97, 0x0A, 0x70, 0xB7, 0x0B, 0x70, 0xC7, 0x0D, 0x70, 0xD7, 0x0E, 0x70, 0xF7, 0x0F, + 0x71, 0x07, 0x11, 0x71, 0x17, 0x12, 0x71, 0x37, 0x13, 0x71, 0x47, 0x15, 0x71, 0x57, 0x16, 0x71, 0x67, 0x17, 0x71, 0x87, 0x18, 0x71, 0x97, 0x1A, + 0x71, 0xA7, 0x1B, 0x71, 0xC7, 0x1C, 0x71, 0xD7, 0x1E, 0x71, 0xE7, 0x1F, 0x71, 0xF7, 0x20, 0x72, 0x17, 0x21, 0x72, 0x27, 0x23, 0x72, 0x37, 0x24, + 0x72, 0x47, 0x25, 0x72, 0x67, 0x26, 0x72, 0x77, 0x28, 0x72, 0x87, 0x29, 0x72, 0x97, 0x2A, 0x72, 0xB7, 0x2B, 0x72, 0xC7, 0x2C, 0x72, 0xD7, 0x2E, + 0x72, 0xE7, 0x2F, 0x72, 0xF7, 0x30, 0x73, 0x17, 0x31, 0x73, 0x27, 0x32, 0x73, 0x37, 0x34, 0x73, 0x47, 0x35, 0x73, 0x57, 0x36, 0x73, 0x77, 0x37, + 0x73, 0x87, 0x38, 0x73, 0x97, 0x3A, 0x73, 0xA7, 0x3B, 0x73, 0xB7, 0x3C, 0x73, 0xC7, 0x3D, 0x73, 0xE7, 0x3E, 0x73, 0xF7, 0x3F, 0x74, 0x07, 0x40, + 0x74, 0x17, 0x42, 0x74, 0x27, 0x43, 0x74, 0x37, 0x44, 0x74, 0x47, 0x45, 0x74, 0x67, 0x46, 0x74, 0x77, 0x47, 0x74, 0x87, 0x48, 0x74, 0x97, 0x49, + 0x74, 0xA7, 0x4B, 0x74, 0xB7, 0x4C, 0x74, 0xC7, 0x4D, 0x74, 0xD7, 0x4E, 0x74, 0xE7, 0x4F, 0x74, 0xF7, 0x50, 0x75, 0x17, 0x51, 0x75, 0x27, 0x52, + 0x75, 0x37, 0x53, 0x75, 0x47, 0x54, 0x75, 0x57, 0x55, 0x75, 0x67, 0x56, 0x75, 0x77, 0x57, 0x75, 0x87, 0x58, 0x75, 0x97, 0x5A, 0x75, 0xA7, 0x5B, + 0x75, 0xB7, 0x5C, 0x75, 0xC7, 0x5D, 0x75, 0xD7, 0x5E, 0x75, 0xE7, 0x5F, 0x75, 0xF7, 0x60, 0x76, 0x07, 0x61, 0x76, 0x17, 0x62, 0x76, 0x27, 0x63, + 0x76, 0x37, 0x64, 0x76, 0x47, 0x65, 0x76, 0x57, 0x66, 0x76, 0x67, 0x67, 0x76, 0x77, 0x68, 0x76, 0x87, 0x69, 0x76, 0x97, 0x6A, 0x76, 0xA7, 0x6B, + 0x76, 0xB7, 0x6C, 0x76, 0xC7, 0x6C, 0x76, 0xD7, 0x6D, 0x76, 0xE7, 0x6E, 0x76, 0xF7, 0x6F, 0x77, 0x07, 0x70, 0x77, 0x17, 0x71, 0x77, 0x27, 0x72, + 0x77, 0x37, 0x73, 0x77, 0x47, 0x74, 0x77, 0x47, 0x75, 0x77, 0x57, 0x76, 0x77, 0x67, 0x77, 0x77, 0x77, 0x78, 0x77, 0x87, 0x79, 0x77, 0x97, 0x79, + 0x77, 0xA7, 0x7A, 0x77, 0xB7, 0x7B, 0x77, 0xC7, 0x7C, 0x77, 0xD7, 0x7D, 0x77, 0xD7, 0x7E, 0x77, 0xE7, 0x7F, 0x77, 0xF7, 0x80, 0x78, 0x07, 0x80, + 0x78, 0x17, 0x81, 0x78, 0x27, 0x82, 0x78, 0x37, 0x83, 0x78, 0x37, 0x84, 0x78, 0x47, 0x85, 0x78, 0x57, 0x85, 0x78, 0x67, 0x86, 0x78, 0x77, 0x87, + 0x78, 0x87, 0x88, 0x78, 0x87, 0x89, 0x78, 0x97, 0x8A, 0x78, 0xA7, 0x8A, 0x78, 0xB7, 0x8B, 0x78, 0xC7, 0x8C, 0x78, 0xC7, 0x8D, 0x78, 0xD7, 0x8E, + 0x78, 0xE7, 0x8E, 0x78, 0xF7, 0x8F, 0x79, 0x07, 0x90, 0x79, 0x07, 0x91, 0x79, 0x17, 0x91, 0x79, 0x27, 0x92, 0x79, 0x37, 0x93, 0x79, 0x37, 0x94, + 0x79, 0x47, 0x94, 0x79, 0x57, 0x95, 0x79, 0x67, 0x96, 0x79, 0x67, 0x97, 0x79, 0x77, 0x97, 0x79, 0x87, 0x98, 0x79, 0x87, 0x99, 0x79, 0x97, 0x9A, + 0x79, 0xA7, 0x9A, 0x79, 0xB7, 0x9B, 0x79, 0xB7, 0x9C, 0x79, 0xC7, 0x9C, 0x79, 0xD7, 0x9D, 0x79, 0xD7, 0x9E, 0x79, 0xE7, 0x9E, 0x79, 0xF7, 0x9F, + 0x79, 0xF7, 0xA0, 0x7A, 0x07, 0xA0, 0x7A, 0x17, 0xA1, 0x7A, 0x17, 0xA2, 0x7A, 0x27, 0xA2, 0x7A, 0x37, 0xA3, 0x7A, 0x37, 0xA4, 0x7A, 0x47, 0xA4, + 0x7A, 0x57, 0xA5, 0x7A, 0x57, 0xA6, 0x7A, 0x67, 0xA6, 0x7A, 0x77, 0xA7, 0x7A, 0x77, 0xA7, 0x7A, 0x87, 0xA8, 0x7A, 0x87, 0xA9, 0x7A, 0x97, 0xA9, + 0x7A, 0xA7, 0xAA, 0x7A, 0xA7, 0xAA, 0x7A, 0xB7, 0xAB, 0x7A, 0xB7, 0xAC, 0x7A, 0xC7, 0xAC, 0x7A, 0xD7, 0xAD, 0x7A, 0xD7, 0xAD, 0x7A, 0xE7, 0xAE, + 0x7A, 0xE7, 0xAE, 0x7A, 0xF7, 0xAF, 0x7A, 0xF7, 0xB0, 0x7B, 0x07, 0xB0, 0x7B, 0x07, 0xB1, 0x7B, 0x17, 0xB1, 0x7B, 0x17, 0xB2, 0x7B, 0x27, 0xB2, + 0x7B, 0x37, 0xB3, 0x7B, 0x37, 0xB3, 0x7B, 0x47, 0xB4, 0x7B, 0x47, 0xB4, 0x7B, 0x57, 0xB5, 0x7B, 0x57, 0xB5, 0x7B, 0x67, 0xB6, 0x7B, 0x67, 0xB6, + 0x7B, 0x77, 0xB7, 0x7B, 0x77, 0xB7, 0x7B, 0x87, 0xB8, 0x7B, 0x87, 0xB8, 0x7B, 0x97, 0xB9, 0x7B, 0x97, 0xB9, 0x7B, 0x97, 0xBA, 0x7B, 0xA7, 0xBA, + 0x7B, 0xA7, 0xBB, 0x7B, 0xB7, 0xBB, 0x7B, 0xB7, 0xBB, 0x7B, 0xC7, 0xBC, 0x7B, 0xC7, 0xBC, 0x7B, 0xD7, 0xBD, 0x7B, 0xD7, 0xBD, 0x7B, 0xD7, 0xBE, + 0x7B, 0xE7, 0xBE, 0x7B, 0xE7, 0xBE, 0x7B, 0xF7, 0xBF, 0x7B, 0xF7, 0xBF, 0x7B, 0xF7, 0xC0, 0x7C, 0x07, 0xC0, 0x7C, 0x07, 0xC0, 0x7C, 0x17, 0xC1, + 0x7C, 0x17, 0xC1, 0x7C, 0x17, 0xC2, 0x7C, 0x27, 0xC2, 0x7C, 0x27, 0xC2, 0x7C, 0x27, 0xC3, 0x7C, 0x37, 0xC3, 0x7C, 0x37, 0xC3, 0x7C, 0x37, 0xC4, + 0x7C, 0x47, 0xC4, 0x7C, 0x47, 0xC4, 0x7C, 0x47, 0xC5, 0x7C, 0x57, 0xC5, 0x7C, 0x57, 0xC5, 0x7C, 0x57, 0xC6, 0x7C, 0x67, 0xC6, 0x7C, 0x67, 0xC6, + 0x7C, 0x67, 0xC7, 0x7C, 0x77, 0xC7, 0x7C, 0x77, 0xC7, 0x7C, 0x77, 0xC7, 0x7C, 0x87, 0xC8, 0x7C, 0x87, 0xC8, 0x7C, 0x87, 0xC8, 0x7C, 0x87, 0xC8, + 0x7C, 0x97, 0xC9, 0x7C, 0x97, 0xC9, 0x7C, 0x97, 0xC9, 0x7C, 0x97, 0xCA, 0x7C, 0xA7, 0xCA, 0x7C, 0xA7, 0xCA, 0x7C, 0xA7, 0xCA, 0x7C, 0xA7, 0xCA, + 0x7C, 0xB7, 0xCB, 0x7C, 0xB7, 0xCB, 0x7C, 0xB7, 0xCB, 0x7C, 0xB7, 0xCB, 0x7C, 0xB7, 0xCC, 0x7C, 0xC7, 0xCC, 0x7C, 0xC7, 0xCC, 0x7C, 0xC7, 0xCC, + 0x7C, 0xC7, 0xCC, 0x7C, 0xC7, 0xCD, 0x7C, 0xD7, 0xCD, 0x7C, 0xD7, 0xCD, 0x7C, 0xD7, 0xCD, 0x7C, 0xD7, 0xCD, 0x7C, 0xD7, 0xCD, 0x7C, 0xD7, 0xCE, + 0x7C, 0xE7, 0xCE, 0x7C, 0xE7, 0xCE, 0x7C, 0xE7, 0xCE, 0x7C, 0xE7, 0xCE, 0x7C, 0xE7, 0xCE, 0x7C, 0xE7, 0xCE, 0x7C, 0xE7, 0xCE, 0x7C, 0xF7, 0xCF, + 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xCF, + 0x7C, 0xF7, 0xCF, 0x7C, 0xF7, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, + 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, 0x7D, 0x07, 0xD0, +}; + +#endif diff --git a/GexUnits/dac/_dac_init.c b/GexUnits/dac/_dac_init.c new file mode 100644 index 0000000..166878e --- /dev/null +++ b/GexUnits/dac/_dac_init.c @@ -0,0 +1,144 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define DAC_INTERNAL +#include "_dac_internal.h" + +/** Allocate data structure and set defaults */ +error_t UDAC_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + for (int i = 0; i < 2; i++) { + priv->cfg.ch[i].buffered = true; + priv->cfg.ch[i].enable = true; + priv->cfg.ch[i].noise_level = 2; + priv->cfg.ch[i].noise_type = NOISE_NONE; + + priv->ch[i].waveform = UDAC_WAVE_DC; + priv->ch[i].dc_level = 2047; + + priv->ch[i].rectangle_ontime = 4096; // half + priv->ch[i].rectangle_high = 4095; + priv->ch[i].rectangle_low = 0; + + priv->ch[i].counter = 0; + priv->ch[i].increment = 0; // stopped + priv->ch[i].phase = 0; + } + + UDAC_SetFreq(unit, 0, 1000); + UDAC_SetFreq(unit, 1, 1000); + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UDAC_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // copy noise config + priv->ch[0].noise_type = priv->cfg.ch[0].noise_type; + priv->ch[0].noise_level = priv->cfg.ch[0].noise_level; + + priv->ch[1].noise_type = priv->cfg.ch[1].noise_type; + priv->ch[1].noise_level = priv->cfg.ch[1].noise_level; + + // this may change for different devices + const Resource r_ch1 = R_PA4; + const Resource r_ch2 = R_PA5; + + TRY(rsc_claim(unit, R_TIM6)); + priv->TIMx = TIM6; + + const bool e1 = priv->cfg.ch[0].enable; + const bool e2 = priv->cfg.ch[1].enable; + + if (e1) { + TRY(rsc_claim(unit, r_ch1)); + } + + if (e2) { + TRY(rsc_claim(unit, r_ch2)); + } + + TRY(rsc_claim(unit, R_DAC1)); + + hw_periph_clock_enable(DAC1); + hw_periph_clock_enable(priv->TIMx); + + GPIO_TypeDef *port; + uint32_t ll; + if (e1) { + assert_param(hw_pinrsc2ll(r_ch1, &port, &ll)); + LL_GPIO_SetPinMode(port, ll, LL_GPIO_MODE_ANALOG); + } + if (e2) { + assert_param(hw_pinrsc2ll(r_ch1, &port, &ll)); + LL_GPIO_SetPinMode(port, ll, LL_GPIO_MODE_ANALOG); + } + + uint16_t presc = 1; + + // presets... TODO pick the highest useable one (or find a new one) +#if UDAC_TIM_FREQ_DIVIDER == 1 + uint32_t count = PLAT_AHB_MHZ; +#elif UDAC_TIM_FREQ_DIVIDER == 2 + uint32_t count = PLAT_AHB_MHZ * 2; +#elif UDAC_TIM_FREQ_DIVIDER == 4 + uint32_t count = PLAT_AHB_MHZ * 4; +#elif UDAC_TIM_FREQ_DIVIDER == 8 + uint32_t count = PLAT_AHB_MHZ * 8; +#else + #error "bad freq" +#endif + +// dbg("Presc %d, count %d", (int)presc, (int)count); + + LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); + LL_TIM_SetAutoReload(priv->TIMx, count - 1); + LL_TIM_EnableARRPreload(priv->TIMx); + LL_TIM_GenerateEvent_UPDATE(priv->TIMx); + LL_TIM_ClearFlag_UPDATE(priv->TIMx); // prevent irq right after enabling + + irqd_attach(priv->TIMx, UDAC_HandleIT, unit); + LL_TIM_EnableIT_UPDATE(priv->TIMx); + + UDAC_Reconfigure(unit); // works with the timer - it should be inited already + + // do not enbale counter initially - no need +// LL_TIM_EnableCounter(priv->TIMx); + + return E_SUCCESS; +} + + +/** Tear down the unit */ +void UDAC_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS ) { + LL_DAC_DeInit(DAC); + LL_TIM_DeInit(priv->TIMx); + + hw_periph_clock_disable(DAC1); + hw_periph_clock_disable(priv->TIMx); + + irqd_detach(priv->TIMx, UDAC_HandleIT); + } + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/dac/_dac_internal.h b/GexUnits/dac/_dac_internal.h new file mode 100644 index 0000000..69b7140 --- /dev/null +++ b/GexUnits/dac/_dac_internal.h @@ -0,0 +1,111 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_DAC_INTERNAL_H +#define GEX_F072_DAC_INTERNAL_H + +#ifndef DAC_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +enum UDAC_Noise { + NOISE_NONE = 0b00, // 0 + NOISE_WHITE = 0b01, // 1 + NOISE_TRIANGLE = 0b10, // 2 +}; + +enum UDAC_Waveform { + UDAC_WAVE_DC, + UDAC_WAVE_SINE, + UDAC_WAVE_TRIANGLE, + UDAC_WAVE_SAWTOOTH_UP, + UDAC_WAVE_SAWTOOTH_DOWN, + UDAC_WAVE_RECTANGLE, +}; + +struct udac_channel_cfg { + bool enable; + bool buffered; + enum UDAC_Noise noise_type; + uint8_t noise_level; // 0-11 +}; + +// 0 - 1 MHz, 2-500k, 4-250k, 8-125k +#define UDAC_TIM_FREQ_DIVIDER 8 + +#define UDAC_INDEX_WIDTH 13 // corresponds to 8192 places +#define UDAC_INDEX_SHIFT (32 - UDAC_INDEX_WIDTH) +#define UDAC_MAX_INDEX ((1 << UDAC_INDEX_WIDTH) - 1) +#define UDAC_VALUE_COUNT (1 << UDAC_INDEX_WIDTH) + +extern const uint8_t LUT_sine_8192_quad_packed[]; + +struct udac_channel_live { + enum UDAC_Noise noise_type; + uint8_t noise_level; // 0-11 + enum UDAC_Waveform waveform; + + uint16_t rectangle_ontime; // for rectangle wave, 0-8191 + uint16_t rectangle_high; + uint16_t rectangle_low; + uint16_t dc_level; // for DC wave + + uint32_t counter; + uint32_t increment; + + // last set phase if the frequencies are the same + // - can be used for live frequency changes without reset (meaningful only with matching increment values) + uint16_t phase; + uint16_t last_index; + uint16_t last_value; +}; + +/** Private data structure */ +struct priv { + // settings + struct { + struct udac_channel_cfg ch[2]; + } cfg; + + // internal state + struct udac_channel_live ch[2]; + TIM_TypeDef *TIMx; // timer used for the DDS function +}; + +/** Allocate data structure and set defaults */ +error_t UDAC_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UDAC_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UDAC_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UDAC_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UDAC_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UDAC_init(Unit *unit); + +/** Tear down the unit */ +void UDAC_deInit(Unit *unit); + +void UDAC_Reconfigure(Unit *unit); + +void UDAC_HandleIT(void *arg); + +error_t UDAC_SetFreq(Unit *unit, int channel, float freq); + +void UDAC_ToggleTimerIfNeeded(Unit *unit); + +#endif //GEX_F072_DAC_INTERNAL_H diff --git a/GexUnits/dac/_dac_settings.c b/GexUnits/dac/_dac_settings.c new file mode 100644 index 0000000..97b9d65 --- /dev/null +++ b/GexUnits/dac/_dac_settings.c @@ -0,0 +1,131 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define DAC_INTERNAL +#include "_dac_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UDAC_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->cfg.ch[0].enable = pp_bool(pp); + priv->cfg.ch[0].buffered = pp_bool(pp); + priv->cfg.ch[0].noise_type = (enum UDAC_Noise) pp_u8(pp); + priv->cfg.ch[0].noise_level = pp_u8(pp); + + priv->cfg.ch[1].enable = pp_bool(pp); + priv->cfg.ch[1].buffered = pp_bool(pp); + priv->cfg.ch[1].noise_type = (enum UDAC_Noise) pp_u8(pp); + priv->cfg.ch[1].noise_level = pp_u8(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void UDAC_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_bool(pb, priv->cfg.ch[0].enable); + pb_bool(pb, priv->cfg.ch[0].buffered); + pb_u8(pb, priv->cfg.ch[0].noise_type); + pb_u8(pb, priv->cfg.ch[0].noise_level); + + pb_bool(pb, priv->cfg.ch[1].enable); + pb_bool(pb, priv->cfg.ch[1].buffered); + pb_u8(pb, priv->cfg.ch[1].noise_type); + pb_u8(pb, priv->cfg.ch[1].noise_level); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UDAC_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + // Ch1 + if (streq(key, "ch1_enable")) { + priv->cfg.ch[0].enable = cfg_bool_parse(value, &suc); + } + else if (streq(key, "ch1_buff")) { + priv->cfg.ch[0].buffered = cfg_bool_parse(value, &suc); + } + else if (streq(key, "ch1_noise")) { + priv->cfg.ch[0].noise_type = + (enum UDAC_Noise) cfg_enum3_parse(value, + "NONE", NOISE_NONE, + "WHITE", NOISE_WHITE, + "TRIANGLE", NOISE_TRIANGLE, &suc); + } + else if (streq(key, "ch1_noise-level")) { + uint8_t x = cfg_u8_parse(value, &suc); + if (x == 0) x = 1; + if (x > 12) x = 12; + priv->cfg.ch[0].noise_level = (uint8_t) (x - 1); + } + // Ch2 + else if (streq(key, "ch2_enable")) { + priv->cfg.ch[1].enable = cfg_bool_parse(value, &suc); + } + else if (streq(key, "ch2_buff")) { + priv->cfg.ch[1].buffered = cfg_bool_parse(value, &suc); + } + else if (streq(key, "ch2_noise")) { + priv->cfg.ch[1].noise_type = + (enum UDAC_Noise) cfg_enum3_parse(value, + "NONE", NOISE_NONE, + "WHITE", NOISE_WHITE, + "TRIANGLE", NOISE_TRIANGLE, &suc); + } + else if (streq(key, "ch2_noise-level")) { + uint8_t x = cfg_u8_parse(value, &suc); + if (x == 0) x = 1; + if (x > 12) x = 12; + priv->cfg.ch[1].noise_level = (uint8_t) (x - 1); + } + // end + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UDAC_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Enabled channels (1:A4, 2:A5)"); + iw_entry_s(iw, "ch1_enable", str_yn(priv->cfg.ch[0].enable)); + iw_entry_s(iw, "ch2_enable", str_yn(priv->cfg.ch[1].enable)); + + iw_comment(iw, "Enable output buffer"); + iw_entry_s(iw, "ch1_buff", str_yn(priv->cfg.ch[0].buffered)); + iw_entry_s(iw, "ch2_buff", str_yn(priv->cfg.ch[1].buffered)); + + iw_comment(iw, "Superimposed noise type (NONE,WHITE,TRIANGLE) and nbr. of bits (1-12)"); + + iw_entry_s(iw, "ch1_noise", cfg_enum3_encode(priv->cfg.ch[0].noise_type, + NOISE_NONE, "NONE", + NOISE_WHITE, "WHITE", + NOISE_TRIANGLE, "TRIANGLE")); + iw_entry_d(iw, "ch1_noise-level", priv->cfg.ch[0].noise_level + 1); + + iw_entry_s(iw, "ch2_noise", cfg_enum3_encode(priv->cfg.ch[1].noise_type, + NOISE_NONE, "NONE", + NOISE_WHITE, "WHITE", + NOISE_TRIANGLE, "TRIANGLE")); + iw_entry_d(iw, "ch2_noise-level", priv->cfg.ch[1].noise_level + 1); +} diff --git a/GexUnits/dac/unit_dac.c b/GexUnits/dac/unit_dac.c new file mode 100644 index 0000000..856187d --- /dev/null +++ b/GexUnits/dac/unit_dac.c @@ -0,0 +1,166 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_dac.h" + +#define DAC_INTERNAL +#include "_dac_internal.h" + +// ------------------------------------------------------------------------ + +// Works OK up to about 20 kHz, could work faster with a faster interrupt +// (may be possible with some optimizations / adjusting priorities...) + +enum DacCmd_ { + CMD_WAVE_DC = 0, + CMD_WAVE_SINE = 1, + CMD_WAVE_TRIANGLE = 2, + CMD_WAVE_SAWTOOTH_UP = 3, + CMD_WAVE_SAWTOOTH_DOWN = 4, + CMD_WAVE_RECTANGLE = 5, + + CMD_SYNC = 10, + + CMD_SET_FREQUENCY = 20, + CMD_SET_PHASE = 21, + CMD_SET_DITHER = 22, +}; + +/** Handle a request message */ +static error_t UDAC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + // Exceptions that aren't per-channel + switch (command) { + case CMD_SYNC: + dbg("Sync"); + priv->ch[0].counter = priv->ch[0].phase << UDAC_INDEX_SHIFT; + priv->ch[1].counter = priv->ch[1].phase << UDAC_INDEX_SHIFT; + return E_SUCCESS; + } + + uint8_t channels = pp_u8(pp); + + bool want_reinit = false; + bool want_tog_timer = false; + + // TODO move this stuff to the api file + + for (int i = 0; i < 2; i++) { + if (channels & (1<ch[i].dc_level = pp_u16(pp); + priv->ch[i].waveform = UDAC_WAVE_DC; + want_tog_timer = true; + break; + + case CMD_WAVE_SINE: + priv->ch[i].waveform = UDAC_WAVE_SINE; + want_tog_timer = true; + break; + + case CMD_WAVE_TRIANGLE: + priv->ch[i].waveform = UDAC_WAVE_TRIANGLE; + want_tog_timer = true; + break; + + case CMD_WAVE_SAWTOOTH_UP: + priv->ch[i].waveform = UDAC_WAVE_SAWTOOTH_UP; + want_tog_timer = true; + break; + + case CMD_WAVE_SAWTOOTH_DOWN: + priv->ch[i].waveform = UDAC_WAVE_SAWTOOTH_DOWN; + want_tog_timer = true; + break; + + case CMD_WAVE_RECTANGLE:; + uint16_t ontime = pp_u16(pp); + uint16_t high = pp_u16(pp); + uint16_t low = pp_u16(pp); + + // use 0xFFFF to skip setting the value + if (high < 4096) priv->ch[i].rectangle_high = high; + if (low < 4096) priv->ch[i].rectangle_low = low; + if (ontime <= UDAC_VALUE_COUNT) priv->ch[i].rectangle_ontime = ontime; + +// dbg("Set rect hi %d, low %d", (int)priv->ch[i].rectangle_high, (int)priv->ch[i].rectangle_low); + + priv->ch[i].waveform = UDAC_WAVE_RECTANGLE; + want_tog_timer = true; + break; + + case CMD_SET_PHASE:; + uint16_t ph = pp_u16(pp); + uint32_t newphase = ph << UDAC_INDEX_SHIFT; + uint32_t oldphase = priv->ch[i].phase << UDAC_INDEX_SHIFT; + int32_t diff = newphase - oldphase; + priv->ch[i].counter += diff; + priv->ch[i].phase = ph; + break; + + case CMD_SET_DITHER:; + uint8_t noisetype = pp_u8(pp); // 0-none, 1-random, 2-triangle + uint8_t noisebits = pp_u8(pp); + + // type 0xFF = not set + if (noisetype <= 2) { + priv->ch[i].noise_type = (enum UDAC_Noise) noisetype; + } + + // bits 0xFF = not set + if (noisebits >= 1 && noisebits <= 12) { + priv->ch[i].noise_level = (uint8_t) (noisebits - 1); + } + +// dbg("Ch %d: Dither type %d, level %d", i, +// (int)priv->ch[i].noise_type, +// (int)priv->ch[i].noise_level); + + want_reinit = true; + break; + + default: + return E_UNKNOWN_COMMAND; + } + } + } + + if (want_reinit) { + UDAC_Reconfigure(unit); + } + + if (want_tog_timer) { + UDAC_ToggleTimerIfNeeded(unit); + } + + return E_SUCCESS; +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_DAC = { + .name = "DAC", + .description = "Two-channel analog output with waveforms", + // Settings + .preInit = UDAC_preInit, + .cfgLoadBinary = UDAC_loadBinary, + .cfgWriteBinary = UDAC_writeBinary, + .cfgLoadIni = UDAC_loadIni, + .cfgWriteIni = UDAC_writeIni, + // Init + .init = UDAC_init, + .deInit = UDAC_deInit, + // Function + .handleRequest = UDAC_handleRequest, +}; diff --git a/GexUnits/dac/unit_dac.h b/GexUnits/dac/unit_dac.h new file mode 100644 index 0000000..b224d8f --- /dev/null +++ b/GexUnits/dac/unit_dac.h @@ -0,0 +1,16 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital input unit; single or multiple pin read access on one port (A-F) +// + +#ifndef U_DAC_H +#define U_DAC_H + +#include "unit.h" + +extern const UnitDriver UNIT_DAC; + +// UU_ prototypes + +#endif //U_DAC_H diff --git a/GexUnits/din/_din_api.c b/GexUnits/din/_din_api.c new file mode 100644 index 0000000..411ac12 --- /dev/null +++ b/GexUnits/din/_din_api.c @@ -0,0 +1,71 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_din.h" + +#define DIN_INTERNAL + +#include "_din_internal.h" + +/** Read request */ +error_t UU_DIn_Read(Unit *unit, uint16_t *packed) +{ + CHECK_TYPE(unit, &UNIT_DIN); + struct priv *priv = unit->data; + *packed = pinmask_pack((uint16_t) priv->port->IDR, priv->pins); + return E_SUCCESS; +} + +/** Arm pins */ +error_t UU_DIn_Arm(Unit *unit, uint16_t arm_single_packed, uint16_t arm_auto_packed) +{ + CHECK_TYPE(unit, &UNIT_DIN); + struct priv *priv = unit->data; + + uint16_t arm_single = pinmask_spread(arm_single_packed, priv->pins); + uint16_t arm_auto = pinmask_spread(arm_auto_packed, priv->pins); + + // abort if user tries to arm pin that doesn't have a trigger configured + if (0 != ((arm_single | arm_auto) & ~(priv->trig_fall | priv->trig_rise))) { + return E_BAD_VALUE; + } + + // arm and reset hold-offs + // we use critical section to avoid irq between the two steps + vPortEnterCritical(); + { + priv->arm_auto |= arm_single; + priv->arm_single |= arm_auto; + const uint16_t combined = arm_single | arm_auto; + for (int i = 0; i < 16; i++) { + if (combined & (1 << i)) { + priv->holdoff_countdowns[i] = 0; + } + } + } + vPortExitCritical(); + + return E_SUCCESS; +} + +/** DisArm pins */ +error_t UU_DIn_DisArm(Unit *unit, uint16_t disarm_packed) +{ + CHECK_TYPE(unit, &UNIT_DIN); + struct priv *priv = unit->data; + + uint16_t disarm = pinmask_spread(disarm_packed, priv->pins); + + // abort if user tries to disarm pin that doesn't have a trigger configured + if (0 != ((disarm) & ~(priv->trig_fall | priv->trig_rise))) { + return E_BAD_VALUE; + } + + priv->arm_auto &= ~disarm; + priv->arm_single &= ~disarm; + + return E_SUCCESS; +} diff --git a/GexUnits/din/_din_exti.c b/GexUnits/din/_din_exti.c new file mode 100644 index 0000000..34a9bd5 --- /dev/null +++ b/GexUnits/din/_din_exti.c @@ -0,0 +1,80 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define DIN_INTERNAL + +#include "_din_internal.h" + +/** + * Send a trigger event to master (called on the message queue thread). + * + * unit - unit + * timestamp - timestamp + * data1 - packed, triggering pin + * data2 - snapshot + */ +static void DIn_SendTriggerReportToMaster(Job *job) +{ + PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); + pb_u16(&pb, (uint16_t) job->data1); // packed, 1 on the triggering pin + pb_u16(&pb, (uint16_t) job->data2); // packed, snapshot + assert_param(pb.ok); + + EventReport event = { + .unit = job->unit, + .timestamp = job->timestamp, + .data = pb.start, + .length = (uint16_t) pb_length(&pb), + }; + + EventReport_Send(&event); +} + +/** + * EXTI callback for pin change interrupts + * + * @param arg - the unit is passed here + */ +void DIn_handleExti(void *arg) +{ + const uint64_t ts = PTIM_GetMicrotime(); + + Unit *unit = arg; + struct priv *priv = unit->data; + const uint16_t snapshot = (uint16_t) priv->port->IDR; + + uint16_t trigger_map = 0; + + uint16_t mask = 1; + const uint16_t armed_pins = priv->arm_single | priv->arm_auto; + for (int i = 0; i < 16; i++, mask <<= 1) { + if (!LL_EXTI_ReadFlag_0_31(LL_EXTI_LINES[i])) continue; + LL_EXTI_ClearFlag_0_31(LL_EXTI_LINES[i]); + + // Armed and ready + if ((armed_pins & mask) && (priv->holdoff_countdowns[i] == 0)) { + // Mark as captured + trigger_map |= (1 << i); + // Start hold-off (no-op if zero hold-off) + priv->holdoff_countdowns[i] = priv->trig_holdoff; + } + } + + // Disarm all possibly used single triggers + priv->arm_single &= ~trigger_map; + + if (trigger_map != 0) { + Job j = { + .unit = unit, + .timestamp = ts, + .data1 = pinmask_pack(trigger_map, priv->pins), + .data2 = pinmask_pack(snapshot, priv->pins), + .cb = DIn_SendTriggerReportToMaster + }; + scheduleJob(&j); + } +} diff --git a/GexUnits/din/_din_init.c b/GexUnits/din/_din_init.c new file mode 100644 index 0000000..ff3edff --- /dev/null +++ b/GexUnits/din/_din_init.c @@ -0,0 +1,139 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define DIN_INTERNAL +#include "_din_internal.h" + +/** Allocate data structure and set defaults */ +error_t DIn_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // some defaults + priv->port_name = 'A'; + priv->pins = 0x0001; + priv->pulldown = 0x0000; + priv->pullup = 0x0000; + + priv->trig_rise = 0x0000; + priv->trig_fall = 0x0000; + priv->trig_holdoff = 100; + priv->def_auto = 0x0000; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t DIn_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + priv->pulldown &= priv->pins; + priv->pullup &= priv->pins; + priv->trig_rise &= priv->pins; + priv->trig_fall &= priv->pins; + priv->def_auto &= (priv->trig_rise|priv->trig_fall); + + // copy auto-arm defaults to the auto-arm register (the register may be manipulated by commands) + priv->arm_auto = priv->def_auto; + priv->arm_single = 0; + + // clear countdowns + memset(priv->holdoff_countdowns, 0, sizeof(priv->holdoff_countdowns)); + + // --- Parse config --- + priv->port = hw_port2periph(priv->port_name, &suc); + if (!suc) return E_BAD_CONFIG; + + // Claim all needed pins + TRY(rsc_claim_gpios(unit, priv->port_name, priv->pins)); + + uint16_t mask; + + // claim the needed EXTIs + mask = 1; + for (int i = 0; i < 16; i++, mask <<= 1) { + if (priv->pins & mask) { + if ((priv->trig_rise|priv->trig_fall) & mask) { + TRY(rsc_claim(unit, R_EXTI0+i)); + } + } + } + + mask = 1; + for (int i = 0; i < 16; i++, mask <<= 1) { + if (priv->pins & mask) { + uint32_t ll_pin = hw_pin2ll((uint8_t) i, &suc); + + // --- Init hardware --- + LL_GPIO_SetPinMode(priv->port, ll_pin, LL_GPIO_MODE_INPUT); + + uint32_t pull = 0; + +#if PLAT_NO_FLOATING_INPUTS + pull = LL_GPIO_PULL_UP; +#else + pull = LL_GPIO_PULL_NO; +#endif + + if (priv->pulldown & mask) pull = LL_GPIO_PULL_DOWN; + if (priv->pullup & mask) pull = LL_GPIO_PULL_UP; + LL_GPIO_SetPinPull(priv->port, ll_pin, pull); + + if ((priv->trig_rise|priv->trig_fall) & mask) { + LL_EXTI_EnableIT_0_31(LL_EXTI_LINES[i]); + + if (priv->trig_rise & mask) { + LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINES[i]); + } + if (priv->trig_fall & mask) { + LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINES[i]); + } + + LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTS[priv->port_name-'A'], LL_SYSCFG_EXTI_LINES[i]); + + irqd_attach(EXTIS[i], DIn_handleExti, unit); + } + } + } + + // request ticks if we have triggers and any hold-offs configured + if ((priv->trig_rise|priv->trig_fall) && priv->trig_holdoff > 0) { + unit->tick_interval = 1; + } + + return E_SUCCESS; +} + + +/** Tear down the unit */ +void DIn_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // pins are de-inited during teardown + + // Detach EXTI handlers and disable interrupts + const uint16_t triggs = priv->trig_rise | priv->trig_fall; + if (unit->status == E_SUCCESS && triggs) { + uint16_t mask = 1; + for (int i = 0; i < 16; i++, mask <<= 1) { + if (triggs & mask) { + LL_EXTI_DisableIT_0_31(LL_EXTI_LINES[i]); + irqd_detach(EXTIS[i], DIn_handleExti); + } + } + } + + // Release all resources + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/din/_din_internal.h b/GexUnits/din/_din_internal.h new file mode 100644 index 0000000..ca010d8 --- /dev/null +++ b/GexUnits/din/_din_internal.h @@ -0,0 +1,65 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_DIN_INTERNAL_H +#define GEX_F072_DIN_INTERNAL_H + +#ifndef DIN_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + char port_name; + uint16_t pins; // pin mask + uint16_t pulldown; // pull-downs (default is pull-up) + uint16_t pullup; // pull-ups + uint16_t trig_rise; // pins generating events on rising edge + uint16_t trig_fall; // pins generating events on falling edge + uint16_t trig_holdoff; // ms + uint16_t def_auto; // initial auto triggers + + uint16_t arm_auto; // pins armed for auto reporting + uint16_t arm_single; // pins armed for single event + uint16_t holdoff_countdowns[16]; // countdowns to arm for each pin in the bit map + GPIO_TypeDef *port; +}; + +/** Allocate data structure and set defaults */ +error_t DIn_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void DIn_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void DIn_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t DIn_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void DIn_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t DIn_init(Unit *unit); + +/** Tear down the unit */ +void DIn_deInit(Unit *unit); + +// ------------------------------------------------------------------------ + +/** + * EXTI callback for pin change interrupts + * + * @param arg - the unit is passed here + */ +void DIn_handleExti(void *arg); + +#endif //GEX_F072_DIN_INTERNAL_H diff --git a/GexUnits/din/_din_settings.c b/GexUnits/din/_din_settings.c new file mode 100644 index 0000000..40d03d5 --- /dev/null +++ b/GexUnits/din/_din_settings.c @@ -0,0 +1,118 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define DIN_INTERNAL +#include "_din_internal.h" + +/** Load from a binary buffer stored in Flash */ +void DIn_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->port_name = pp_char(pp); + priv->pins = pp_u16(pp); + priv->pulldown = pp_u16(pp); + priv->pullup = pp_u16(pp); + priv->trig_rise = pp_u16(pp); + priv->trig_fall = pp_u16(pp); + priv->trig_holdoff = pp_u16(pp); + priv->def_auto = pp_u16(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void DIn_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_char(pb, priv->port_name); + pb_u16(pb, priv->pins); + pb_u16(pb, priv->pulldown); + pb_u16(pb, priv->pullup); + pb_u16(pb, priv->trig_rise); + pb_u16(pb, priv->trig_fall); + pb_u16(pb, priv->trig_holdoff); + pb_u16(pb, priv->def_auto); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t DIn_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "port")) { + suc = cfg_port_parse(value, &priv->port_name); + } + else if (streq(key, "pins")) { + priv->pins = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "pull-up")) { + priv->pullup = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "pull-down")) { + priv->pulldown = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "trig-rise")) { + priv->trig_rise = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "trig-fall")) { + priv->trig_fall = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "auto-trigger")) { + priv->def_auto = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "hold-off")) { + priv->trig_holdoff = cfg_u16_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void DIn_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Port name"); + iw_entry(iw, "port", "%c", priv->port_name); + + iw_comment(iw, "Pins (comma separated, supports ranges)"); + iw_entry_s(iw, "pins", cfg_pinmask_encode(priv->pins, unit_tmp512, 0)); + + iw_comment(iw, "Pins with pull-up"); + iw_entry_s(iw, "pull-up", cfg_pinmask_encode(priv->pullup, unit_tmp512, 0)); + + iw_comment(iw, "Pins with pull-down"); + iw_entry_s(iw, "pull-down", cfg_pinmask_encode(priv->pulldown, unit_tmp512, 0)); + + iw_cmt_newline(iw); + iw_comment(iw, "Trigger pins activated by rising/falling edge"); + iw_entry_s(iw, "trig-rise", cfg_pinmask_encode(priv->trig_rise, unit_tmp512, 0)); + iw_entry_s(iw, "trig-fall", cfg_pinmask_encode(priv->trig_fall, unit_tmp512, 0)); + + iw_comment(iw, "Trigger pins auto-armed by default"); + iw_entry_s(iw, "auto-trigger", cfg_pinmask_encode(priv->def_auto, unit_tmp512, 0)); + + iw_comment(iw, "Triggers hold-off time (ms)"); + iw_entry_d(iw, "hold-off", priv->trig_holdoff); + +#if PLAT_NO_FLOATING_INPUTS + iw_comment(iw, "NOTE: Pins use pull-up by default.\r\n"); +#endif +} + diff --git a/GexUnits/din/unit_din.c b/GexUnits/din/unit_din.c new file mode 100644 index 0000000..0e66784 --- /dev/null +++ b/GexUnits/din/unit_din.c @@ -0,0 +1,94 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_din.h" + +#define DIN_INTERNAL +#include "_din_internal.h" + +// ------------------------------------------------------------------------ + +enum PinCmd_ { + CMD_READ = 0, + CMD_ARM_SINGLE = 1, + CMD_ARM_AUTO = 2, + CMD_DISARM = 3, +}; + +/** Handle a request message */ +static error_t DIn_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + uint16_t pins = 0; + + switch (command) { + case CMD_READ:; + TRY(UU_DIn_Read(unit, &pins)); + + PayloadBuilder pb = pb_start((uint8_t*)unit_tmp512, UNIT_TMP_LEN, NULL); + pb_u16(&pb, pins); // packed input pins + com_respond_buf(frame_id, MSG_SUCCESS, (uint8_t *) unit_tmp512, pb_length(&pb)); + return E_SUCCESS; + + case CMD_ARM_SINGLE:; + pins = pp_u16(pp); + if (!pp->ok) return E_MALFORMED_COMMAND; + + TRY(UU_DIn_Arm(unit, pins, 0)); + return E_SUCCESS; + + case CMD_ARM_AUTO:; + pins = pp_u16(pp); + if (!pp->ok) return E_MALFORMED_COMMAND; + + TRY(UU_DIn_Arm(unit, 0, pins)); + return E_SUCCESS; + + case CMD_DISARM:; + pins = pp_u16(pp); + if (!pp->ok) return E_MALFORMED_COMMAND; + + TRY(UU_DIn_DisArm(unit, pins)); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +/** + * Decrement all the hold-off timers on tick + * + * @param unit + */ +static void DIn_updateTick(Unit *unit) +{ + struct priv *priv = unit->data; + + for (int i = 0; i < 16; i++) { + if (priv->holdoff_countdowns[i] > 0) { + priv->holdoff_countdowns[i]--; + } + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_DIN = { + .name = "DI", + .description = "Digital input with triggers", + // Settings + .preInit = DIn_preInit, + .cfgLoadBinary = DIn_loadBinary, + .cfgWriteBinary = DIn_writeBinary, + .cfgLoadIni = DIn_loadIni, + .cfgWriteIni = DIn_writeIni, + // Init + .init = DIn_init, + .deInit = DIn_deInit, + // Function + .handleRequest = DIn_handleRequest, + .updateTick = DIn_updateTick, +}; diff --git a/GexUnits/din/unit_din.h b/GexUnits/din/unit_din.h new file mode 100644 index 0000000..cab6a63 --- /dev/null +++ b/GexUnits/din/unit_din.h @@ -0,0 +1,42 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital input unit; single or multiple pin read access on one port (A-F) +// + +#ifndef U_DIN_H +#define U_DIN_H + +#include "unit.h" + +extern const UnitDriver UNIT_DIN; + +/** + * Read pins + * + * @param unit - unit instance + * @param packed - output; the packed (right aligned) bits representing the pins, highest to lowest, are written here. + * @return success + */ +error_t UU_DIn_Read(Unit *unit, uint16_t *packed); + +/** + * Arm pins for trigger generation + * + * @param unit - unit instance + * @param arm_single_packed - packed bit map of pins to arm for single trigger + * @param arm_auto_packed - packed bit map of pins to arm for auto trigger (repeated) + * @return success + */ +error_t UU_DIn_Arm(Unit *unit, uint16_t arm_single_packed, uint16_t arm_auto_packed); + +/** + * Dis-arm pins to not generate events + * + * @param unit - unit instance + * @param disarm_packed - packed bit map of pins to dis-arm + * @return success + */ +error_t UU_DIn_DisArm(Unit *unit, uint16_t disarm_packed); + +#endif //U_DIN_H diff --git a/GexUnits/dout/_dout_api.c b/GexUnits/dout/_dout_api.c new file mode 100644 index 0000000..f297e55 --- /dev/null +++ b/GexUnits/dout/_dout_api.c @@ -0,0 +1,173 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_dout.h" + +#define DOUT_INTERNAL + +#include "_dout_internal.h" + +static void clear_pulse_by_mask(struct priv *priv, uint16_t spread) +{ + assert_param(priv); + + for (int i = 0; i < 16; i++) { + if (spread & (1 << i)) { + priv->msec_pulse_cnt[i] = 0; + } + } +} + +error_t UU_DOut_Write(Unit *unit, uint16_t packed) +{ + CHECK_TYPE(unit, &UNIT_DOUT); + + struct priv *priv = unit->data; + uint16_t mask = priv->pins; + uint16_t spread = pinmask_spread(packed, mask); + clear_pulse_by_mask(priv, spread); + + uint16_t set = spread; + uint16_t reset = ((~spread) & mask); + priv->port->BSRR = set | (reset << 16); + return E_SUCCESS; +} + +error_t UU_DOut_Set(Unit *unit, uint16_t packed) +{ + CHECK_TYPE(unit, &UNIT_DOUT); + + struct priv *priv = unit->data; + uint16_t mask = priv->pins; + uint16_t spread = pinmask_spread(packed, mask); + clear_pulse_by_mask(priv, spread); + + priv->port->BSRR = spread; + return E_SUCCESS; +} + +error_t UU_DOut_Clear(Unit *unit, uint16_t packed) +{ + CHECK_TYPE(unit, &UNIT_DOUT); + + struct priv *priv = unit->data; + uint16_t mask = priv->pins; + uint16_t spread = pinmask_spread(packed, mask); + clear_pulse_by_mask(priv, spread); + + priv->port->BSRR = (spread << 16); + return E_SUCCESS; +} + +error_t UU_DOut_Toggle(Unit *unit, uint16_t packed) +{ + CHECK_TYPE(unit, &UNIT_DOUT); + + struct priv *priv = unit->data; + uint16_t mask = priv->pins; + uint16_t spread = pinmask_spread(packed, mask); + clear_pulse_by_mask(priv, spread); + + uint16_t flipped = (uint16_t) (~priv->port->ODR) & mask; + uint16_t set = flipped & spread; + uint16_t reset = ((~flipped) & mask) & spread; + priv->port->BSRR = set | (reset << 16); + return E_SUCCESS; +} + +error_t UU_DOut_GetPinCount(Unit *unit, uint8_t *count) +{ + CHECK_TYPE(unit, &UNIT_DOUT); + struct priv *priv = unit->data; + + uint32_t packed = pinmask_pack(0xFFFF, priv->pins); + *count = (uint8_t) (32 - __CLZ(packed)); + return E_SUCCESS; +} + +error_t UU_DOut_Pulse(Unit *unit, uint16_t packed, bool polarity, bool is_usec, uint16_t count) +{ + CHECK_TYPE(unit, &UNIT_DOUT); + struct priv *priv = unit->data; + assert_param(priv); + + uint16_t mask = priv->pins; + uint16_t spread = pinmask_spread(packed, mask); + clear_pulse_by_mask(priv, spread); + + vPortEnterCritical(); + + if (is_usec) { + // we're gonna do this right here as a delay loop. + if (count >= 1000) { + // too long, fall back to msec + count /= 1000; + is_usec = false; + } + else { + const uint32_t bsrr1 = spread << (polarity ? 0 : 16); + const uint32_t bsrr0 = spread << (polarity ? 16 : 0); + + const uint32_t start = PTIM_MicroDelayAlign(); + priv->port->BSRR = bsrr1; + PTIM_MicroDelayAligned(count, start); + priv->port->BSRR = bsrr0; + } + } + + if (!is_usec) { + // Load the counters + for (int i = 0; i < 16; i++) { + if (spread & (1 << i)) { + priv->msec_pulse_cnt[i] = (uint16_t) (count + 1); + } + } + + if (polarity) { + priv->msec_pulse_scheduled_1 |= spread; + } else { + priv->msec_pulse_scheduled_0 |= spread; + } + + unit->_tick_cnt = 0; + unit->tick_interval = 1; + } + + vPortExitCritical(); + + return E_SUCCESS; +} + +void DOut_Tick(Unit *unit) +{ + struct priv *priv = unit->data; + + uint16_t odr = (uint16_t) priv->port->ODR; + int live_cnt = 0; + for (int i = 0; i < 16; i++) { + if (priv->msec_pulse_scheduled_1 & (1<msec_pulse_scheduled_0 & (1<msec_pulse_cnt[i] > 0) { + live_cnt++; + priv->msec_pulse_cnt[i]--; + if (priv->msec_pulse_cnt[i] == 0) { + odr ^= 1 << i; + } + } + } + priv->port->ODR = odr; + priv->msec_pulse_scheduled_1 = 0; + priv->msec_pulse_scheduled_0 = 0; + + if (live_cnt == 0) { + unit->_tick_cnt = 0; + unit->tick_interval = 0; + } +} diff --git a/GexUnits/dout/_dout_init.c b/GexUnits/dout/_dout_init.c new file mode 100644 index 0000000..8ee2a0c --- /dev/null +++ b/GexUnits/dout/_dout_init.c @@ -0,0 +1,71 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define DOUT_INTERNAL +#include "_dout_internal.h" + +/** Allocate data structure and set defaults */ +error_t DOut_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // some defaults + priv->port_name = 'A'; + priv->pins = 0x0001; + priv->open_drain = 0x0000; + priv->initial = 0x0000; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t DOut_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + priv->initial &= priv->pins; + priv->open_drain &= priv->pins; + + // --- Parse config --- + priv->port = hw_port2periph(priv->port_name, &suc); + if (!suc) return E_BAD_CONFIG; + + // Claim all needed pins + TRY(rsc_claim_gpios(unit, priv->port_name, priv->pins)); + + for (int i = 0; i < 16; i++) { + if (priv->pins & (1 << i)) { + uint32_t ll_pin = hw_pin2ll((uint8_t) i, &suc); + + // --- Init hardware --- + LL_GPIO_SetPinMode(priv->port, ll_pin, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(priv->port, ll_pin, + (priv->open_drain & (1 << i)) ? LL_GPIO_OUTPUT_OPENDRAIN : LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinSpeed(priv->port, ll_pin, LL_GPIO_SPEED_FREQ_HIGH); + } + } + + // Set the initial state + priv->port->ODR &= ~priv->pins; + priv->port->ODR |= priv->initial; + + return E_SUCCESS; +} + +/** Tear down the unit */ +void DOut_deInit(Unit *unit) +{ + // pins are de-inited during teardown + + // Release all resources + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/dout/_dout_internal.h b/GexUnits/dout/_dout_internal.h new file mode 100644 index 0000000..15a5fed --- /dev/null +++ b/GexUnits/dout/_dout_internal.h @@ -0,0 +1,54 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_DOUT_INTERNAL_H +#define GEX_F072_DOUT_INTERNAL_H + +#ifndef DOUT_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + char port_name; + uint16_t pins; // pin mask + uint16_t initial; // initial pin states + uint16_t open_drain; // open drain pins + + GPIO_TypeDef *port; + uint16_t msec_pulse_cnt[16]; + uint16_t msec_pulse_scheduled_1; + uint16_t msec_pulse_scheduled_0; +}; + +/** Allocate data structure and set defaults */ +error_t DOut_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void DOut_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void DOut_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t DOut_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void DOut_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t DOut_init(Unit *unit); + +/** Tear down the unit */ +void DOut_deInit(Unit *unit); + +void DOut_Tick(Unit *unit); + +#endif //GEX_F072_DOUT_INTERNAL_H diff --git a/GexUnits/dout/_dout_settings.c b/GexUnits/dout/_dout_settings.c new file mode 100644 index 0000000..f792153 --- /dev/null +++ b/GexUnits/dout/_dout_settings.c @@ -0,0 +1,82 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define DOUT_INTERNAL +#include "_dout_internal.h" + +/** Load from a binary buffer stored in Flash */ +void DOut_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->port_name = pp_char(pp); + priv->pins = pp_u16(pp); + priv->initial = pp_u16(pp); + priv->open_drain = pp_u16(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void DOut_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_char(pb, priv->port_name); + pb_u16(pb, priv->pins); + pb_u16(pb, priv->initial); + pb_u16(pb, priv->open_drain); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t DOut_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "port")) { + suc = cfg_port_parse(value, &priv->port_name); + } + else if (streq(key, "pins")) { + priv->pins = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "initial")) { + priv->initial = cfg_pinmask_parse(value, &suc); + } + else if (streq(key, "open-drain")) { + priv->open_drain = cfg_pinmask_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void DOut_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Port name"); + iw_entry(iw, "port", "%c", priv->port_name); + + iw_comment(iw, "Pins (comma separated, supports ranges)"); + iw_entry_s(iw, "pins", cfg_pinmask_encode(priv->pins, unit_tmp512, 0)); + + iw_comment(iw, "Initially high pins"); + iw_entry_s(iw, "initial", cfg_pinmask_encode(priv->initial, unit_tmp512, 0)); + + iw_comment(iw, "Open-drain pins"); + iw_entry_s(iw, "open-drain", cfg_pinmask_encode(priv->open_drain, unit_tmp512, 0)); +} diff --git a/GexUnits/dout/unit_dout.c b/GexUnits/dout/unit_dout.c new file mode 100644 index 0000000..4839546 --- /dev/null +++ b/GexUnits/dout/unit_dout.c @@ -0,0 +1,66 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_dout.h" + +#define DOUT_INTERNAL +#include "_dout_internal.h" + +enum PinCmd_ { + CMD_WRITE = 0, + CMD_SET = 1, + CMD_CLEAR = 2, + CMD_TOGGLE = 3, + CMD_PULSE = 4, +}; + +/** Handle a request message */ +static error_t DOut_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + uint16_t packed = pp_u16(pp); + + switch (command) { + case CMD_WRITE: + return UU_DOut_Write(unit, packed); + + case CMD_SET: + return UU_DOut_Set(unit, packed); + + case CMD_CLEAR: + return UU_DOut_Clear(unit, packed); + + case CMD_TOGGLE: + return UU_DOut_Toggle(unit, packed); + + case CMD_PULSE:; + bool polarity = pp_bool(pp); + bool is_usec = pp_bool(pp); + uint16_t count = pp_u16(pp); + return UU_DOut_Pulse(unit, packed, polarity, is_usec, count); + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_DOUT = { + .name = "DO", + .description = "Digital output", + // Settings + .preInit = DOut_preInit, + .cfgLoadBinary = DOut_loadBinary, + .cfgWriteBinary = DOut_writeBinary, + .cfgLoadIni = DOut_loadIni, + .cfgWriteIni = DOut_writeIni, + // Init + .init = DOut_init, + .deInit = DOut_deInit, + // Function + .handleRequest = DOut_handleRequest, + .updateTick = DOut_Tick, +}; diff --git a/GexUnits/dout/unit_dout.h b/GexUnits/dout/unit_dout.h new file mode 100644 index 0000000..3e66a50 --- /dev/null +++ b/GexUnits/dout/unit_dout.h @@ -0,0 +1,71 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital output unit; single or multiple pin write access on one port (A-F) +// + +#ifndef U_DOUT_H +#define U_DOUT_H + +#include "unit.h" + +extern const UnitDriver UNIT_DOUT; + +/** + * Write pins (e.g. writing 0b10 if configured pins are PA5 and PA0 sets PA5=1,PA0=0) + * + * @param unit + * @param packed - packed pin states (aligned to right) + * @return success + */ +error_t UU_DOut_Write(Unit *unit, uint16_t packed); + +/** + * Set pins (clear none) + * + * @param unit + * @param packed - packed pins, 1 if pin should be set + * @return success + */ +error_t UU_DOut_Set(Unit *unit, uint16_t packed); + +/** + * Clear multiple pins + * + * @param unit + * @param packed - packed pins, 1 if pin should be cleared + * @return + */ +error_t UU_DOut_Clear(Unit *unit, uint16_t packed); + +/** + * Toggle pins + * + * @param unit + * @param packed - packed pins, 1 if pin should be toggled + * @return + */ +error_t UU_DOut_Toggle(Unit *unit, uint16_t packed); + +/** + * Get number of configured pins + * + * @param unit + * @param count output, written with 0-16 + * @return success + */ +error_t UU_DOut_GetPinCount(Unit *unit, uint8_t *count); + +/** + * Send a pulse + * + * @param unit + * @param packed - pins to pulse + * @param polarity - pulse active level + * @param is_usec - use usec precision (for < 1 ms) + * @param count - number of units (msec or usec) + * @return success + */ +error_t UU_DOut_Pulse(Unit *unit, uint16_t packed, bool polarity, bool is_usec, uint16_t count); + +#endif //U_DOUT_H diff --git a/GexUnits/fcap/_fcap_api.c b/GexUnits/fcap/_fcap_api.c new file mode 100644 index 0000000..81e1d9e --- /dev/null +++ b/GexUnits/fcap/_fcap_api.c @@ -0,0 +1,11 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_fcap.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + diff --git a/GexUnits/fcap/_fcap_core.c b/GexUnits/fcap/_fcap_core.c new file mode 100644 index 0000000..3725394 --- /dev/null +++ b/GexUnits/fcap/_fcap_core.c @@ -0,0 +1,461 @@ +// +// Created by MightyPork on 2018/02/20. +// + +#include "platform.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +static void UFCAP_StopMeasurement(Unit *unit); +static void UFCAP_ConfigureForIndirectCapture(Unit *unit); +static void UFCAP_ConfigureForDirectCapture(Unit *unit, uint16_t msec); +static void UFCAP_ConfigureForFreeCapture(Unit *unit); + +uint32_t UFCAP_GetFreeCounterValue(Unit *unit) +{ + struct priv * const priv = unit->data; + TIM_TypeDef * const TIMx = priv->TIMx; + return TIMx->CNT; +} + +uint32_t UFCAP_FreeCounterClear(Unit *unit) +{ + struct priv * const priv = unit->data; + TIM_TypeDef * const TIMx = priv->TIMx; + + // this isn't perfect, we can miss one clock + // but it's probably the best we can do here ... + vPortEnterCritical(); + uint32_t val = TIMx->CNT; + TIMx->CNT = 0; + vPortExitCritical(); + + return val; +} + +static void UFCAP_IndirectBurstReportJob(Job *job) +{ + Unit *unit = job->unit; + struct priv * const priv = unit->data; + + uint8_t buf[20]; + PayloadBuilder pb = pb_start(buf, 20, NULL); + + pb_u16(&pb, PLAT_AHB_MHZ); + pb_u16(&pb, priv->ind_burst.n_count); + pb_u64(&pb, priv->ind_burst.period_acu); + pb_u64(&pb, priv->ind_burst.ontime_acu); + + assert_param(pb.ok); + + com_respond_pb(priv->request_id, MSG_SUCCESS, &pb); + + // timer is already stopped, now in OPMODE_BUSY + priv->opmode = OPMODE_IDLE; +} + +static void UFCAP_SinglePulseReportJob(Job *job) +{ + Unit *unit = job->unit; + struct priv * const priv = unit->data; + + uint8_t buf[6]; + PayloadBuilder pb = pb_start(buf, 6, NULL); + + pb_u16(&pb, PLAT_AHB_MHZ); + pb_u32(&pb, job->data1); + assert_param(pb.ok); + + com_respond_pb(priv->request_id, MSG_SUCCESS, &pb); + + // timer is already stopped, now in OPMODE_BUSY + priv->opmode = OPMODE_IDLE; +} + +/** + * Count is passed in data1 + * @param job + */ +static void UFCAP_DirectBurstReportJob(Job *job) +{ + Unit *unit = job->unit; + struct priv * const priv = unit->data; + + uint8_t buf[8]; + PayloadBuilder pb = pb_start(buf, 8, NULL); + + pb_u8(&pb, priv->direct_presc); + pb_u16(&pb, priv->dir_burst.msec); + pb_u32(&pb, job->data1); + + assert_param(pb.ok); + + com_respond_pb(priv->request_id, MSG_SUCCESS, &pb); + + // timer is already stopped, now in OPMODE_BUSY + priv->opmode = OPMODE_IDLE; +} + +void UFCAP_TIMxHandler(void *arg) +{ + Unit *unit = arg; + assert_param(unit); + struct priv * const priv = unit->data; + assert_param(priv); + + TIM_TypeDef * const TIMx = priv->TIMx; + + if (priv->opmode == OPMODE_INDIRECT_CONT) { + if (LL_TIM_IsActiveFlag_CC1(TIMx)) { + if (priv->n_skip > 0) { + priv->n_skip--; + } else { + priv->ind_cont.last_period = LL_TIM_IC_GetCaptureCH1(TIMx); + priv->ind_cont.last_ontime = priv->ind_cont.ontime; + } + LL_TIM_ClearFlag_CC1(TIMx); + LL_TIM_ClearFlag_CC1OVR(TIMx); + } + + if (LL_TIM_IsActiveFlag_CC2(TIMx)) { + priv->ind_cont.ontime = LL_TIM_IC_GetCaptureCH2(TIMx); + LL_TIM_ClearFlag_CC2(TIMx); + LL_TIM_ClearFlag_CC2OVR(TIMx); + } + } + else if (priv->opmode == OPMODE_SINGLE_PULSE) { + if (LL_TIM_IsActiveFlag_CC2(TIMx)) { + // single pulse - does not wait for the second edge + uint32_t len = LL_TIM_IC_GetCaptureCH2(TIMx); + + priv->opmode = OPMODE_BUSY; + UFCAP_StopMeasurement(unit); + + Job j = { + .cb = UFCAP_SinglePulseReportJob, + .unit = unit, + .data1 = len, + }; + scheduleJob(&j); + } + } + else if (priv->opmode == OPMODE_INDIRECT_BURST) { + if (LL_TIM_IsActiveFlag_CC1(TIMx)) { + const uint32_t period = LL_TIM_IC_GetCaptureCH1(TIMx); + const uint32_t ontime = priv->ind_burst.ontime; + + if (priv->n_skip > 0) { + priv->n_skip--; + } else { + priv->ind_burst.ontime_acu += ontime; + priv->ind_burst.period_acu += period; + if (++priv->ind_burst.n_count == priv->ind_burst.n_target) { + priv->opmode = OPMODE_BUSY; + UFCAP_StopMeasurement(unit); + + Job j = { + .cb = UFCAP_IndirectBurstReportJob, + .unit = unit, + }; + scheduleJob(&j); + } + } + + LL_TIM_ClearFlag_CC1(TIMx); + LL_TIM_ClearFlag_CC1OVR(TIMx); + } + + if (LL_TIM_IsActiveFlag_CC2(TIMx)) { + priv->ind_burst.ontime = LL_TIM_IC_GetCaptureCH2(TIMx); + LL_TIM_ClearFlag_CC2(TIMx); + LL_TIM_ClearFlag_CC2OVR(TIMx); + } + } + else if (priv->opmode == OPMODE_IDLE) { + // clear everything - in idle it would cycle in the handler forever + TIMx->SR = 0; + } + else { + trap("Unhandled fcap TIMx irq"); + } +} + +void UFCAP_TIMyHandler(void *arg) +{ + Unit *unit = arg; + assert_param(unit); + struct priv *const priv = unit->data; + assert_param(priv); + + TIM_TypeDef * const TIMx = priv->TIMx; + TIM_TypeDef * const TIMy = priv->TIMy; + uint32_t cnt = TIMx->CNT; // TIMx should be stopped now + +// dbg("> TIMy Handler, TIMx cntr is %d", cnt); + priv->dir_cont.last_count = cnt; + + if (priv->opmode == OPMODE_DIRECT_CONT) { + LL_TIM_DisableCounter(TIMx); + LL_TIM_DisableCounter(TIMy); + LL_TIM_SetCounter(TIMx, 0); + LL_TIM_SetCounter(TIMy, 0); + LL_TIM_EnableCounter(TIMy); // next loop + LL_TIM_EnableCounter(TIMx); + } + else if (priv->opmode == OPMODE_DIRECT_BURST) { + priv->opmode = OPMODE_BUSY; + UFCAP_StopMeasurement(unit); + + Job j = { + .cb = UFCAP_DirectBurstReportJob, + .unit = unit, + .data1 = cnt, + }; + scheduleJob(&j); + } + else if (priv->opmode == OPMODE_IDLE) { + // clear everything - in idle it would cycle in the handler forever + TIMy->SR = 0; + } + else { + trap("Unhandled fcap TIMy irq"); + } + + LL_TIM_ClearFlag_UPDATE(TIMy); +} + +static void UFCAP_ClearTimerConfig(Unit *unit) +{ + struct priv * const priv = unit->data; + TIM_TypeDef * const TIMx = priv->TIMx; + + // CLEAR CURRENT STATE, STOP + UFCAP_StopMeasurement(unit); + + // CONFIGURE TIMER BASIC PARAMS + LL_TIM_SetPrescaler(TIMx, 0); + LL_TIM_SetAutoReload(TIMx, 0xFFFFFFFF); + LL_TIM_EnableARRPreload(TIMx); + LL_TIM_GenerateEvent_UPDATE(TIMx); +} + +/** + * Reset all timer registers + * + * @param unit + */ +static void UFCAP_StopMeasurement(Unit *unit) +{ + struct priv * const priv = unit->data; + + LL_TIM_DeInit(priv->TIMx); // clear all flags and settings + LL_TIM_DeInit(priv->TIMy); // clear all flags and settings +} + +/** + * Switch the FCAP module opmode + * + * @param unit + * @param opmode + */ +void UFCAP_SwitchMode(Unit *unit, enum fcap_opmode opmode) +{ + struct priv * const priv = unit->data; + + if (opmode == priv->opmode) return; + + priv->opmode = opmode; + + switch (opmode) { + case OPMODE_IDLE: + // XXX maybe we should report the abort to the PC-side listener + UFCAP_StopMeasurement(unit); + break; + + case OPMODE_INDIRECT_CONT: + priv->ind_cont.last_ontime = 0; + priv->ind_cont.last_period = 0; + priv->ind_cont.ontime = 0; + priv->n_skip = 1; // discard the first cycle (will be incomplete) + UFCAP_ConfigureForIndirectCapture(unit); // is also stopped and restarted + break; + + case OPMODE_INDIRECT_BURST: + priv->ind_burst.ontime = 0; + priv->ind_burst.n_count = 0; + priv->ind_burst.period_acu = 0; + priv->ind_burst.ontime_acu = 0; + priv->n_skip = 1; // discard the first cycle (will be incomplete) + UFCAP_ConfigureForIndirectCapture(unit); // is also stopped and restarted + break; + + case OPMODE_SINGLE_PULSE: + priv->n_skip = 0; + UFCAP_ConfigureForIndirectCapture(unit); // is also stopped and restarted + break; + + case OPMODE_DIRECT_CONT: + // msec is set by caller + priv->dir_cont.last_count = 0; + priv->n_skip = 1; // discard the first cycle (will be incomplete) + UFCAP_ConfigureForDirectCapture(unit, priv->direct_msec); + break; + + case OPMODE_DIRECT_BURST: + // msec is set by caller + priv->n_skip = 0; // no skip here (if there was any) + UFCAP_ConfigureForDirectCapture(unit, (uint16_t) priv->dir_burst.msec); + break; + + case OPMODE_FREE_COUNTER: + UFCAP_ConfigureForFreeCapture(unit); + break; + + default: + trap("Unhandled opmode %d", (int)opmode); + } +} + +/** + * Configure peripherals for an indirect capture (PWM measurement) - continuous or burst + * @param unit + */ +static void UFCAP_ConfigureForIndirectCapture(Unit *unit) +{ + struct priv * const priv = unit->data; + TIM_TypeDef * const TIMx = priv->TIMx; + const uint32_t ll_ch_a = priv->ll_ch_a; + const uint32_t ll_ch_b = priv->ll_ch_b; + + UFCAP_ClearTimerConfig(unit); + + // Enable channels and select mapping to TIx signals + + // A - will be used to measure period + // B - will be used to measure the duty cycle + + // _________ ______ + // _______| |________________| + // A B A + // irq irq,cap irq + // reset + + // B irq may be used if we want to measure a pulse width + + // Normally TI1 = CH1, TI2 = CH2. + // It's possible to select the other channel, which we use to connect both TIx to the shame CHx. + LL_TIM_IC_SetActiveInput(TIMx, ll_ch_a, priv->a_direct ? LL_TIM_ACTIVEINPUT_DIRECTTI : LL_TIM_ACTIVEINPUT_INDIRECTTI); + LL_TIM_IC_SetActiveInput(TIMx, ll_ch_b, priv->a_direct ? LL_TIM_ACTIVEINPUT_INDIRECTTI : LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPolarity(TIMx, ll_ch_a, priv->active_level ? LL_TIM_IC_POLARITY_RISING : LL_TIM_IC_POLARITY_FALLING); + LL_TIM_IC_SetPolarity(TIMx, ll_ch_b, priv->active_level ? LL_TIM_IC_POLARITY_FALLING : LL_TIM_IC_POLARITY_RISING); + + if (priv->dfilter > 15) priv->dfilter = 15; + uint32_t filter = LL_TIM_IC_FILTERS[priv->dfilter]; + LL_TIM_IC_SetFilter(TIMx, ll_ch_a, filter); + LL_TIM_IC_SetFilter(TIMx, ll_ch_b, filter); + + LL_TIM_CC_EnableChannel(TIMx, ll_ch_a | ll_ch_b); + + LL_TIM_SetSlaveMode(TIMx, LL_TIM_SLAVEMODE_RESET); + LL_TIM_SetTriggerInput(TIMx, LL_TIM_TS_TI1FP1); // Use Filtered Input 1 (TI1) + + LL_TIM_EnableMasterSlaveMode(TIMx); + + LL_TIM_ClearFlag_CC1(TIMx); + LL_TIM_ClearFlag_CC1OVR(TIMx); + LL_TIM_ClearFlag_CC2(TIMx); + LL_TIM_ClearFlag_CC2OVR(TIMx); + + LL_TIM_EnableIT_CC1(TIMx); + LL_TIM_EnableIT_CC2(TIMx); + LL_TIM_EnableCounter(TIMx); +} + + +/** + * Configure peripherals for an indirect capture (PWM measurement) - continuous or burst + * @param unit + */ +static void UFCAP_ConfigureForDirectCapture(Unit *unit, uint16_t msec) +{ + struct priv * const priv = unit->data; + +// dbg("Configuring Direct capture..."); + + UFCAP_ClearTimerConfig(unit); + + { + TIM_TypeDef *const TIMy = priv->TIMy; + assert_param(PLAT_AHB_MHZ<=65); + uint16_t presc = PLAT_AHB_MHZ*1000; + uint32_t count = msec+1; // it's one tick longer because we generate OCREF on the exact msec count - it must be at least 1 tick long + + LL_TIM_SetPrescaler(TIMy, (uint32_t) (presc - 1)); + LL_TIM_SetAutoReload(TIMy, count - 1); + LL_TIM_EnableARRPreload(TIMy); + LL_TIM_GenerateEvent_UPDATE(TIMy); + LL_TIM_SetOnePulseMode(TIMy, LL_TIM_ONEPULSEMODE_SINGLE); + LL_TIM_OC_EnableFast(TIMy, LL_TIM_CHANNEL_CH1); + +// dbg("TIMy presc %d, count %d", (int) presc, (int) count); + + LL_TIM_SetTriggerOutput(TIMy, LL_TIM_TRGO_OC1REF); + LL_TIM_OC_SetMode(TIMy, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1); // 1 until CC, then 0 + LL_TIM_OC_SetCompareCH1(TIMy, count-1); + LL_TIM_CC_EnableChannel(TIMy, LL_TIM_CHANNEL_CH1); // enable the output channel that produces a trigger + + LL_TIM_ClearFlag_UPDATE(TIMy); + LL_TIM_EnableIT_UPDATE(TIMy); + } + + { + // TIMx - the slave + TIM_TypeDef *const TIMx = priv->TIMx; + LL_TIM_SetSlaveMode(TIMx, LL_TIM_SLAVEMODE_GATED); + LL_TIM_SetTriggerInput(TIMx, LL_TIM_TS_ITR3); // ITR3 is TIM14 which we use as TIMy + LL_TIM_EnableMasterSlaveMode(TIMx); + + uint32_t presc = LL_TIM_ETR_PRESCALER_DIV1; + switch (priv->direct_presc) { + case 1: presc = LL_TIM_ETR_PRESCALER_DIV1; break; + case 2: presc = LL_TIM_ETR_PRESCALER_DIV2; break; + case 4: presc = LL_TIM_ETR_PRESCALER_DIV4; break; + case 8: presc = LL_TIM_ETR_PRESCALER_DIV8; break; + default: + priv->direct_presc = 1; // will be sent with the response + } + + if (priv->dfilter > 15) priv->dfilter = 15; + uint32_t filter = LL_TIM_ETR_FILTERS[priv->dfilter]; + + LL_TIM_ConfigETR(TIMx, + priv->active_level ? LL_TIM_ETR_POLARITY_NONINVERTED : LL_TIM_ETR_POLARITY_INVERTED, + presc, + filter); + + LL_TIM_EnableExternalClock(TIMx); // TODO must check and deny this mode if the pin is not on CH1 = external trigger input + + LL_TIM_SetCounter(TIMx, 0); + LL_TIM_EnableCounter(TIMx); + } + + LL_TIM_EnableCounter(priv->TIMy); // XXX this will start the first pulse (maybe) +} + + +/** + * Freerunning capture (counting pulses - geiger) + * @param unit + */ +static void UFCAP_ConfigureForFreeCapture(Unit *unit) +{ + struct priv * const priv = unit->data; + + UFCAP_ClearTimerConfig(unit); + + TIM_TypeDef *const TIMx = priv->TIMx; + LL_TIM_EnableExternalClock(TIMx); + LL_TIM_SetCounter(TIMx, 0); + LL_TIM_EnableCounter(TIMx); +} diff --git a/GexUnits/fcap/_fcap_init.c b/GexUnits/fcap/_fcap_init.c new file mode 100644 index 0000000..cc79e41 --- /dev/null +++ b/GexUnits/fcap/_fcap_init.c @@ -0,0 +1,147 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +/** Allocate data structure and set defaults */ +error_t UFCAP_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + priv->conf.signal_pname = 'A'; + priv->conf.signal_pnum = 0; + + priv->conf.active_level = 1; + priv->conf.direct_presc = 1; + priv->conf.dfilter = 0; + priv->conf.direct_msec = 1000; + priv->conf.startmode = OPMODE_IDLE; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UFCAP_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // ---- Resolve what to configure ---- + + TIM_TypeDef * const TIMx = TIM2; + Resource timRsc = R_TIM2; + + TIM_TypeDef * const TIMy = TIM14; + Resource tim2Rsc = R_TIM14; + + uint32_t ll_ch_a = 0; + uint32_t ll_ch_b = 0; + + switch (priv->conf.signal_pname) { + case 'A': + switch (priv->conf.signal_pnum) { + case 5: + case 15: + case 0: ll_ch_a = LL_TIM_CHANNEL_CH1; break; + case 1: ll_ch_a = LL_TIM_CHANNEL_CH2; break; + default: + dbg("Bad signal pin!"); + return E_BAD_CONFIG; + } + break; + case 'B': + switch (priv->conf.signal_pnum) { + case 3: ll_ch_a = LL_TIM_CHANNEL_CH2; break; + default: + dbg("Bad signal pin!"); + return E_BAD_CONFIG; + } + break; + + default: + dbg("Bad signal pin port!"); + return E_BAD_CONFIG; + } + const uint32_t ll_timpin_af = LL_GPIO_AF_2; + + bool a_direct = true; + switch (ll_ch_a) { + case LL_TIM_CHANNEL_CH1: + ll_ch_b = LL_TIM_CHANNEL_CH2; + break; + + case LL_TIM_CHANNEL_CH2: + ll_ch_b = LL_TIM_CHANNEL_CH1; + a_direct = false; + break; + } + + // ---- CLAIM ---- + + TRY(rsc_claim_pin(unit, priv->conf.signal_pname, priv->conf.signal_pnum)); + TRY(rsc_claim(unit, timRsc)); + TRY(rsc_claim(unit, tim2Rsc)); + + // ---- INIT ---- + assert_param(ll_ch_a != ll_ch_b); + + priv->TIMx = TIMx; + priv->TIMy = TIMy; + priv->ll_ch_a = ll_ch_a; + priv->ll_ch_b = ll_ch_b; + priv->a_direct = a_direct; + + // Load defaults + priv->active_level = priv->conf.active_level; + priv->direct_presc = priv->conf.direct_presc; + priv->dfilter = priv->conf.dfilter; + priv->direct_msec = priv->conf.direct_msec; + priv->opmode = priv->conf.startmode; + + TRY(hw_configure_gpio_af(priv->conf.signal_pname, priv->conf.signal_pnum, ll_timpin_af)); + + GPIO_TypeDef *gpio = hw_port2periph(priv->conf.signal_pname, &suc); + uint32_t ll_pin = hw_pin2ll(priv->conf.signal_pnum, &suc); + LL_GPIO_SetPinPull(gpio, ll_pin, LL_GPIO_PULL_DOWN); // XXX change to pull-up if the polarity is inverted + + hw_periph_clock_enable(TIMx); + hw_periph_clock_enable(TIMy); + irqd_attach(TIMx, UFCAP_TIMxHandler, unit); + irqd_attach(TIMy, UFCAP_TIMyHandler, unit); + + UFCAP_SwitchMode(unit, priv->opmode); // switch to the default opmode + + return E_SUCCESS; +} + +/** Tear down the unit */ +void UFCAP_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS ) { + UFCAP_SwitchMode(unit, OPMODE_IDLE); + + TIM_TypeDef *TIMx = priv->TIMx; + TIM_TypeDef *TIMy = priv->TIMy; + LL_TIM_DeInit(TIMx); + LL_TIM_DeInit(TIMy); + irqd_detach(TIMx, UFCAP_TIMxHandler); + irqd_detach(TIMy, UFCAP_TIMyHandler); + hw_periph_clock_disable(TIMx); + hw_periph_clock_disable(TIMy); + } + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/fcap/_fcap_internal.h b/GexUnits/fcap/_fcap_internal.h new file mode 100644 index 0000000..9111322 --- /dev/null +++ b/GexUnits/fcap/_fcap_internal.h @@ -0,0 +1,117 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_FCAP_INTERNAL_H +#define GEX_F072_FCAP_INTERNAL_H + +#ifndef FCAP_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +enum fcap_opmode { + OPMODE_IDLE = 0, + OPMODE_BUSY = 1, // used after capture is done, before it's reported + OPMODE_INDIRECT_CONT = 2, + OPMODE_INDIRECT_BURST = 3, // averaging + OPMODE_DIRECT_CONT = 4, + OPMODE_DIRECT_BURST = 5, + OPMODE_FREE_COUNTER = 6, + OPMODE_SINGLE_PULSE = 7, +}; + +/** Private data structure */ +struct priv { + // settings + struct { + char signal_pname; // the input pin - one of TIM2 channels + uint8_t signal_pnum; + + bool active_level; + uint8_t direct_presc; + uint8_t dfilter; + uint16_t direct_msec; + + enum fcap_opmode startmode; + } conf; + + // internal state + TIM_TypeDef *TIMx; + TIM_TypeDef *TIMy; // used as a timebase source for TIMx in direct mode + uint32_t ll_ch_b; + uint32_t ll_ch_a; + bool a_direct; + + enum fcap_opmode opmode; + + TF_ID request_id; + uint8_t n_skip; //!< Periods to skip before starting the real capture + + bool active_level; // in PWM mode, the first part that is measured. (if 1, HHHLLL, else LLLHHH). In direct mode, clock polarity + uint8_t direct_presc; + uint16_t direct_msec; + uint8_t dfilter; + + union { + struct { + uint32_t ontime; // length of the captured positive pulse in the current interval + uint32_t last_period; //!< length of the captured interval between two rising edges + uint32_t last_ontime; //!< length of the last captured ontime + } ind_cont; + + struct { + uint32_t ontime; // length of the captured positive pulse in the current interval + uint64_t period_acu; //!< length of the captured interval between two rising edges, sum + uint64_t ontime_acu; //!< length of the last captured ontime, sum + uint16_t n_count; //!< Periods captured + uint16_t n_target; //!< Periods captured - requested count + } ind_burst; + + struct { + uint32_t last_count; //!< Pulse count in the last capture window + } dir_cont; + + struct { + uint16_t msec; // capture window length (used in the report callback) - different from the cont time, which is a semi-persistent config + } dir_burst; + }; +}; + +/** Allocate data structure and set defaults */ +error_t UFCAP_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UFCAP_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UFCAP_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UFCAP_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UFCAP_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UFCAP_init(Unit *unit); + +/** Tear down the unit */ +void UFCAP_deInit(Unit *unit); + +// ------------------------------------------------------------------------ + +void UFCAP_SwitchMode(Unit *unit, enum fcap_opmode opmode); + +void UFCAP_TIMxHandler(void *arg); +void UFCAP_TIMyHandler(void *arg); + +uint32_t UFCAP_GetFreeCounterValue(Unit *unit); +uint32_t UFCAP_FreeCounterClear(Unit *unit); + +#endif //GEX_F072_FCAP_INTERNAL_H diff --git a/GexUnits/fcap/_fcap_settings.c b/GexUnits/fcap/_fcap_settings.c new file mode 100644 index 0000000..37ddfdc --- /dev/null +++ b/GexUnits/fcap/_fcap_settings.c @@ -0,0 +1,114 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UFCAP_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->conf.signal_pname = pp_char(pp); + priv->conf.signal_pnum = pp_u8(pp); + priv->conf.active_level = pp_bool(pp); + priv->conf.dfilter = pp_u8(pp); + priv->conf.direct_presc = pp_u8(pp); + priv->conf.direct_msec = pp_u16(pp); + priv->conf.startmode = (enum fcap_opmode) pp_u8(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void UFCAP_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_char(pb, priv->conf.signal_pname); + pb_u8(pb, priv->conf.signal_pnum); + pb_bool(pb, priv->conf.active_level); + pb_u8(pb, priv->conf.dfilter); + pb_u8(pb, priv->conf.direct_presc); + pb_u16(pb, priv->conf.direct_msec); + pb_u8(pb, priv->conf.startmode); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UFCAP_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "pin")) { + suc = cfg_portpin_parse(value, &priv->conf.signal_pname, &priv->conf.signal_pnum); + } + else if (streq(key, "active-level")) { + priv->conf.active_level = cfg_bool_parse(value, &suc); + } + else if (streq(key, "input-filter")) { + priv->conf.dfilter = cfg_u8_parse(value, &suc); + } + else if (streq(key, "direct-presc")) { + priv->conf.direct_presc = cfg_u8_parse(value, &suc); + } + else if (streq(key, "direct-time")) { + priv->conf.direct_msec = cfg_u16_parse(value, &suc); + } + else if (streq(key, "initial-mode")) { + priv->conf.startmode = (enum fcap_opmode) cfg_enum4_parse(value, + "N", OPMODE_IDLE, + "I", OPMODE_INDIRECT_CONT, + "D", OPMODE_DIRECT_CONT, + "F", OPMODE_FREE_COUNTER, + &suc); + } + else{ + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UFCAP_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Signal input pin - one of:"); + iw_comment(iw, " Full support: A0, A5, A15"); + iw_comment(iw, " Indirect only: A1, B3"); + iw_entry(iw, "pin", "%c%d", priv->conf.signal_pname, priv->conf.signal_pnum); + iw_cmt_newline(iw); + + iw_comment(iw, "Active level or edge (0-low,falling; 1-high,rising)"); + iw_entry_d(iw, "active-level", priv->conf.active_level); + + iw_comment(iw, "Input filtering (0-15)"); + iw_entry_d(iw, "input-filter", priv->conf.dfilter); + + iw_comment(iw, "Pulse counter pre-divider (1,2,4,8)"); + iw_entry_d(iw, "direct-presc", priv->conf.direct_presc); + + iw_comment(iw, "Pulse counting interval (ms)"); + iw_entry_d(iw, "direct-time", priv->conf.direct_msec); + iw_cmt_newline(iw); + + iw_comment(iw, "Mode on startup: N-none, I-indirect, D-direct, F-free count"); + iw_entry_s(iw, "initial-mode", cfg_enum4_encode(priv->conf.startmode, + OPMODE_IDLE, "N", + OPMODE_INDIRECT_CONT, "I", + OPMODE_DIRECT_CONT, "D", + OPMODE_FREE_COUNTER, "F")); +} + diff --git a/GexUnits/fcap/unit_fcap.c b/GexUnits/fcap/unit_fcap.c new file mode 100644 index 0000000..863b700 --- /dev/null +++ b/GexUnits/fcap/unit_fcap.c @@ -0,0 +1,322 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_fcap.h" + +#define FCAP_INTERNAL +#include "_fcap_internal.h" + +// ------------------------------------------------------------------------ + +enum FcapCmd_ { + CMD_STOP = 0, + + // Measuring a waveform + CMD_INDIRECT_CONT_START = 1, // keep measuring, read on demand + CMD_INDIRECT_BURST_START = 2, // wait and reply + + // Counting pulses + CMD_DIRECT_CONT_START = 3, // keep measuring, read on demand + CMD_DIRECT_BURST_START = 4, // wait and reply + CMD_FREECOUNT_START = 5, // keep counting pulses until stopped, read on reply + + CMD_MEASURE_SINGLE_PULSE = 6, // measure the first incoming pulse of the right polarity. NOTE: can glitch if the signal starts in the active level + CMD_FREECOUNT_CLEAR = 7, // clear the free counter, return last value + + // Results readout for continuous modes + CMD_INDIRECT_CONT_READ = 10, + CMD_DIRECT_CONT_READ = 11, + CMD_FREECOUNT_READ = 12, + + // configs + CMD_SET_POLARITY = 20, + CMD_SET_DIR_PRESC = 21, + CMD_SET_INPUT_FILTER = 22, + CMD_SET_DIR_MSEC = 23, + + // go back to the configured settings + CMD_RESTORE_DEFAULTS = 30, +}; + +/** Handle a request message */ +static error_t UFCAP_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + uint8_t presc; + uint16_t msec; + + struct priv *priv = unit->data; + PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); + const char* msg_denied_on_pin = "Not available on the selected pin!"; + + switch (command) { + /** + * Stop any ongoing measurement and return to base state. + */ + case CMD_STOP: + UFCAP_SwitchMode(unit, OPMODE_IDLE); + return E_SUCCESS; + + // ----------------------- CONFIG -------------------------- + + /** + * Set the active polarity, or triggering edge (for direct) + * + * pld: pol:u8 (0,1) + */ + case CMD_SET_POLARITY: + { + priv->active_level = pp_bool(pp); + } + return E_SUCCESS; + + /** + * Set the direct measurement prescaller 1,2,4,8 + * + * pld: presc:u8 + */ + case CMD_SET_DIR_PRESC: + { + presc = pp_u8(pp); + if (presc != 1 && presc != 2 && presc != 4 && presc != 8) return E_BAD_VALUE; + priv->direct_presc = presc; + } + return E_SUCCESS; + + /** + * Set the input filter for all modes + * + * pld: filter:u8 (0-15) + */ + case CMD_SET_INPUT_FILTER: + { + uint8_t input_filter = pp_u8(pp); + if (input_filter >= 16) return E_BAD_VALUE; + priv->dfilter = input_filter; + } + return E_SUCCESS; + + /** + * Set the direct sampling time. + * + * pld: msec:u16 + */ + case CMD_SET_DIR_MSEC: + { + msec = pp_u16(pp); + priv->direct_msec = msec; + } + return E_SUCCESS; + + /** + * Reset all SET* settings to their default values, stop any ongoing measure. + */ + case CMD_RESTORE_DEFAULTS: + UFCAP_SwitchMode(unit, OPMODE_IDLE); + + priv->active_level = priv->conf.active_level; + priv->direct_presc = priv->conf.direct_presc; + priv->direct_msec = priv->conf.direct_msec; + priv->dfilter = priv->conf.dfilter; + return E_SUCCESS; + + // ------------------ COMMANDS ------------------------ + + /** + * Start indirect continuous measurement. + */ + case CMD_INDIRECT_CONT_START: + if (priv->opmode == OPMODE_INDIRECT_CONT) return E_SUCCESS; // no-op + if (priv->opmode != OPMODE_IDLE) return E_BUSY; + + UFCAP_SwitchMode(unit, OPMODE_INDIRECT_CONT); + return E_SUCCESS; + + /** + * Start a continuous direct measurement (counting pulses in fixed time intervals) + * + * - meas_time_ms 0 = no change + * - prescaller 0 = no change + * + * pld: meas_time_ms:u16, prescaller:u8 + * - prescaller is 1,2,4,8; 0 = no change + */ + case CMD_DIRECT_CONT_START: + if (!priv->a_direct) { + // This works only if we use the ETR pin. TIM2 shares CH1 with ETR. + // If CH2 is selected as input, ETR is not available. + com_respond_str(MSG_ERROR, frame_id, msg_denied_on_pin); + return E_FAILURE; + } + if (priv->opmode == OPMODE_DIRECT_CONT) return E_SUCCESS; // no-op + if (priv->opmode != OPMODE_IDLE) return E_BUSY; + + msec = pp_u16(pp); + presc = pp_u8(pp); + if (msec != 0) priv->direct_msec = msec; + if (presc != 0) priv->direct_presc = presc; + + UFCAP_SwitchMode(unit, OPMODE_DIRECT_CONT); + return E_SUCCESS; + + /** + * Start a burst of direct measurements with averaging. + * The measurement is performed on N consecutive pulses. + * + * pld: count:u16 + * + * resp: core_mhz:u16, count:u16, period_sum:u64, ontime_sum:u64 + */ + case CMD_INDIRECT_BURST_START: + if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; + + priv->ind_burst.n_target = pp_u16(pp); + priv->request_id = frame_id; + UFCAP_SwitchMode(unit, OPMODE_INDIRECT_BURST); + return E_SUCCESS; + + /** + * Start a single direct measurement of the given length (pulses in time period) + * If 'prescaller' is not 0, it is changed via the param field. + * + * pld: meas_time_ms:u16, prescaller:u8 + * - prescaller is 1,2,4,8; 0 = no change + * + * resp: prescaller:u8, meas_time_ms:u16, pulse_count:u32 + */ + case CMD_DIRECT_BURST_START: + if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; + + priv->dir_burst.msec = pp_u16(pp); + + presc = pp_u8(pp); + if (presc != 0) priv->direct_presc = presc; + + priv->request_id = frame_id; + UFCAP_SwitchMode(unit, OPMODE_DIRECT_BURST); + return E_SUCCESS; + + /** + * Measure a single pulse length of the given polarity. + * Measures time from a rising to a falling edge (or falling to rising, if polarity is 0) + * + * resp: core_mhz:u16, ontime:u32 + */ + case CMD_MEASURE_SINGLE_PULSE: + if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; + priv->request_id = frame_id; + UFCAP_SwitchMode(unit, OPMODE_SINGLE_PULSE); + return E_SUCCESS; + + /** + * Start a free-running pulse counter. + * + * pld: prescaller:u8 + * - prescaller is 1,2,4,8; 0 = no change + */ + case CMD_FREECOUNT_START: + if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; + + presc = pp_u8(pp); + if (presc != 0) priv->direct_presc = presc; + + UFCAP_SwitchMode(unit, OPMODE_FREE_COUNTER); + return E_SUCCESS; + + /** + * Reset the free-running pulse counter. + * + * resp: last_val:u32 + */ + case CMD_FREECOUNT_CLEAR: + if (priv->opmode != OPMODE_FREE_COUNTER) { + return E_BAD_MODE; + } + + pb_u32(&pb, UFCAP_FreeCounterClear(unit)); + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + // ------------------ READING --------------------- + + /** + * Read the most recent pulse measurement during continuous indirect measure. + * + * resp: core_mhz:u16, period:u32, ontime:u32 + */ + case CMD_INDIRECT_CONT_READ: + if (priv->opmode != OPMODE_INDIRECT_CONT) { + return E_BAD_MODE; + } + if (priv->ind_cont.last_period == 0) { + return E_BUSY; + } + + pb_u16(&pb, PLAT_AHB_MHZ); + pb_u32(&pb, priv->ind_cont.last_period); + pb_u32(&pb, priv->ind_cont.last_ontime); + + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Read the most recent result of a continuous direct measurement. + * + * resp: prescaller:u8, meas_time_ms:u16, pulse_count:u32 + */ + case CMD_DIRECT_CONT_READ: + if (!priv->a_direct) { // see above + com_respond_str(MSG_ERROR, frame_id, msg_denied_on_pin); + return E_FAILURE; + } + if (priv->opmode != OPMODE_DIRECT_CONT) return E_BAD_MODE; + if (priv->dir_cont.last_count == 0) return E_BUSY; + + pb_u8(&pb, priv->direct_presc); + pb_u16(&pb, priv->direct_msec); + pb_u32(&pb, priv->dir_cont.last_count); + + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Read the current value of the free-running pulse counter. + * + * The timing may have a significant jitter, this function is practically useful only for + * slow pulse sources (like a geiger counter, item counting etc) + * + * resp: count:u32 + */ + case CMD_FREECOUNT_READ: + if (priv->opmode != OPMODE_FREE_COUNTER) { + return E_BAD_MODE; + } + + pb_u32(&pb, UFCAP_GetFreeCounterValue(unit)); + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Frequency capture */ +const UnitDriver UNIT_FCAP = { + .name = "FCAP", + .description = "Frequency and pulse measurement", + // Settings + .preInit = UFCAP_preInit, + .cfgLoadBinary = UFCAP_loadBinary, + .cfgWriteBinary = UFCAP_writeBinary, + .cfgLoadIni = UFCAP_loadIni, + .cfgWriteIni = UFCAP_writeIni, + // Init + .init = UFCAP_init, + .deInit = UFCAP_deInit, + // Function + .handleRequest = UFCAP_handleRequest, +}; diff --git a/GexUnits/fcap/unit_fcap.h b/GexUnits/fcap/unit_fcap.h new file mode 100644 index 0000000..f3faf58 --- /dev/null +++ b/GexUnits/fcap/unit_fcap.h @@ -0,0 +1,16 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital input unit; single or multiple pin read access on one port (A-F) +// + +#ifndef U_FCAP_H +#define U_FCAP_H + +#include "unit.h" + +extern const UnitDriver UNIT_FCAP; + +// UU_ prototypes + +#endif //U_FCAP_H diff --git a/GexUnits/i2c/_i2c_api.c b/GexUnits/i2c/_i2c_api.c new file mode 100644 index 0000000..d302e28 --- /dev/null +++ b/GexUnits/i2c/_i2c_api.c @@ -0,0 +1,123 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_i2c.h" + +#define I2C_INTERNAL +#include "_i2c_internal.h" + +static void i2c_reset(struct priv *priv) +{ + LL_I2C_Disable(priv->periph); + HAL_Delay(1); + LL_I2C_Enable(priv->periph); +} + +static error_t i2c_wait_until_flag(struct priv *priv, uint32_t flag, bool stop_state) +{ + uint32_t t_start = HAL_GetTick(); + while (((priv->periph->ISR & flag)!=0) != stop_state) { + if (HAL_GetTick() - t_start > 10) { + i2c_reset(priv); + return E_HW_TIMEOUT; + } + } + return E_SUCCESS; +} + +error_t UU_I2C_Write(Unit *unit, uint16_t addr, const uint8_t *bytes, uint32_t bcount) +{ + CHECK_TYPE(unit, &UNIT_I2C); + + struct priv *priv = unit->data; + + uint8_t addrsize = (uint8_t) (((addr & 0x8000) == 0) ? 7 : 10); + addr &= 0x3FF; + uint32_t ll_addrsize = (addrsize == 7) ? LL_I2C_ADDRSLAVE_7BIT : LL_I2C_ADDRSLAVE_10BIT; + if (addrsize == 7) addr <<= 1; // 7-bit address must be shifted to left for LL to use it correctly + + TRY(i2c_wait_until_flag(priv, I2C_ISR_BUSY, 0)); + + bool first = true; + while (bcount > 0) { + uint32_t len = bcount; + uint32_t chunk_remain = (uint8_t) ((len > 255) ? 255 : len); // if more than 255, first chunk is 255 + LL_I2C_HandleTransfer(priv->periph, addr, ll_addrsize, chunk_remain, + (len > 255) ? LL_I2C_MODE_RELOAD : LL_I2C_MODE_AUTOEND, // Autoend if this is the last chunk + first ? LL_I2C_GENERATE_START_WRITE : LL_I2C_GENERATE_NOSTARTSTOP); // no start/stop condition if we're continuing + first = false; + bcount -= chunk_remain; + + for (; chunk_remain > 0; chunk_remain--) { + TRY(i2c_wait_until_flag(priv, I2C_ISR_TXIS, 1)); + uint8_t byte = *bytes++; + LL_I2C_TransmitData8(priv->periph, byte); + } + } + + TRY(i2c_wait_until_flag(priv, I2C_ISR_STOPF, 1)); + LL_I2C_ClearFlag_STOP(priv->periph); + return E_SUCCESS; +} + +error_t UU_I2C_Read(Unit *unit, uint16_t addr, uint8_t *dest, uint32_t bcount) +{ + CHECK_TYPE(unit, &UNIT_I2C); + + struct priv *priv = unit->data; + + uint8_t addrsize = (uint8_t) (((addr & 0x8000) == 0) ? 7 : 10); + addr &= 0x3FF; + uint32_t ll_addrsize = (addrsize == 7) ? LL_I2C_ADDRSLAVE_7BIT : LL_I2C_ADDRSLAVE_10BIT; + if (addrsize == 7) addr <<= 1; // 7-bit address must be shifted to left for LL to use it correctly + + TRY(i2c_wait_until_flag(priv, I2C_ISR_BUSY, 0)); + + bool first = true; + while (bcount > 0) { + if (!first) { + TRY(i2c_wait_until_flag(priv, I2C_ISR_TCR, 1)); + } + + uint8_t chunk_remain = (uint8_t) ((bcount > 255) ? 255 : bcount); // if more than 255, first chunk is 255 + LL_I2C_HandleTransfer(priv->periph, addr, ll_addrsize, chunk_remain, + (bcount > 255) ? LL_I2C_MODE_RELOAD : LL_I2C_MODE_AUTOEND, // Autoend if this is the last chunk + first ? LL_I2C_GENERATE_START_READ : LL_I2C_GENERATE_NOSTARTSTOP); // no start/stop condition if we're continuing + first = false; + bcount -= chunk_remain; + + for (; chunk_remain > 0; chunk_remain--) { + TRY(i2c_wait_until_flag(priv, I2C_ISR_RXNE, 1)); + + uint8_t byte = LL_I2C_ReceiveData8(priv->periph); + *dest++ = byte; + } + } + + TRY(i2c_wait_until_flag(priv, I2C_ISR_STOPF, 1)); + LL_I2C_ClearFlag_STOP(priv->periph); + return E_SUCCESS; +} + +error_t UU_I2C_ReadReg(Unit *unit, uint16_t addr, uint8_t regnum, uint8_t *dest, uint32_t width) +{ + TRY(UU_I2C_Write(unit, addr, ®num, 1)); + TRY(UU_I2C_Read(unit, addr, dest, width)); + return E_SUCCESS; +} + +error_t UU_I2C_WriteReg(Unit *unit, uint16_t addr, uint8_t regnum, const uint8_t *bytes, uint32_t width) +{ + CHECK_TYPE(unit, &UNIT_I2C); + + // we have to insert the address first - needs a buffer (XXX realistically the buffer needs 1-4 bytes + addr) + PayloadBuilder pb = pb_start((uint8_t*)unit_tmp512, UNIT_TMP_LEN, NULL); + pb_u8(&pb, regnum); + pb_buf(&pb, bytes, width); + + TRY(UU_I2C_Write(unit, addr, (uint8_t *) unit_tmp512, pb_length(&pb))); + return E_SUCCESS; +} diff --git a/GexUnits/i2c/_i2c_init.c b/GexUnits/i2c/_i2c_init.c new file mode 100644 index 0000000..4e53468 --- /dev/null +++ b/GexUnits/i2c/_i2c_init.c @@ -0,0 +1,160 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define I2C_INTERNAL +#include "_i2c_internal.h" + + +/** Allocate data structure and set defaults */ +error_t UI2C_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // some defaults + priv->periph_num = 1; + priv->speed = 1; + priv->anf = true; + priv->dnf = 0; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UI2C_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (!(priv->periph_num >= 1 && priv->periph_num <= 2)) { + dbg("!! Bad I2C periph"); // TODO report + return E_BAD_CONFIG; + } + + if (!(priv->speed >= 1 && priv->speed <= 3)) { + dbg("!! Bad I2C speed"); + return E_BAD_CONFIG; + } + + if (priv->dnf > 15) { + dbg("!! Bad I2C DNF bw"); + return E_BAD_CONFIG; + } + + // assign and claim the peripheral + if (priv->periph_num == 1) { + TRY(rsc_claim(unit, R_I2C1)); + priv->periph = I2C1; + } else { + TRY(rsc_claim(unit, R_I2C2)); + priv->periph = I2C2; + } + + // This is written for F072, other platforms will need adjustments + + char portname; + uint8_t pin_scl; + uint8_t pin_sda; + uint32_t af_i2c; + uint32_t timing; // magic constant from CubeMX + +#if STM32F072xB + // scl - 6 or 8 for I2C1, 10 for I2C2 + // sda - 7 or 9 for I2C1, 11 for I2C2 + if (priv->periph_num == 1) { + // I2C1 + if (priv->remap == 0) { + af_i2c = LL_GPIO_AF_1; + portname = 'B'; + pin_scl = 6; + pin_sda = 7; + } else if (priv->remap == 1) { + af_i2c = LL_GPIO_AF_1; + portname = 'B'; + pin_scl = 8; + pin_sda = 9; + } else { + return E_BAD_CONFIG; + } + } else { + // I2C2 + if (priv->remap == 0) { + af_i2c = LL_GPIO_AF_1; + portname = 'B'; + pin_scl = 10; + pin_sda = 11; + } else if (priv->remap == 1) { + af_i2c = LL_GPIO_AF_5; + portname = 'B'; + pin_scl = 13; + pin_sda = 14; + } else { + return E_BAD_CONFIG; + } + } + + if (priv->speed == 1) + timing = 0x00301D2B; // Standard + else if (priv->speed == 2) + timing = 0x0000020B; // Fast + else + timing = 0x00000001; // Fast+ + +#elif GEX_PLAT_F103_BLUEPILL + #error "NO IMPL" +#elif GEX_PLAT_F303_DISCOVERY + #error "NO IMPL" +#elif GEX_PLAT_F407_DISCOVERY + #error "NO IMPL" +#else + #error "BAD PLATFORM!" +#endif + + // first, we have to claim the pins + TRY(rsc_claim_pin(unit, portname, pin_sda)); + TRY(rsc_claim_pin(unit, portname, pin_scl)); + + TRY(hw_configure_gpio_af(portname, pin_sda, af_i2c)); + TRY(hw_configure_gpio_af(portname, pin_scl, af_i2c)); + + hw_periph_clock_enable(priv->periph); + + /* Disable the selected I2Cx Peripheral */ + LL_I2C_Disable(priv->periph); + LL_I2C_ConfigFilters(priv->periph, + (priv->anf ? LL_I2C_ANALOGFILTER_ENABLE : LL_I2C_ANALOGFILTER_DISABLE), + priv->dnf); + + LL_I2C_SetTiming(priv->periph, timing); + //LL_I2C_DisableClockStretching(priv->periph); + LL_I2C_Enable(priv->periph); + + LL_I2C_DisableOwnAddress1(priv->periph); // OA not used + LL_I2C_SetMode(priv->periph, LL_I2C_MODE_I2C); // not using SMBus + + return E_SUCCESS; +} + +/** Tear down the unit */ +void UI2C_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init the pins & peripheral only if inited correctly + if (unit->status == E_SUCCESS) { + assert_param(priv->periph); + LL_I2C_DeInit(priv->periph); + + hw_periph_clock_disable(priv->periph); + } + + // Release all resources + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/i2c/_i2c_internal.h b/GexUnits/i2c/_i2c_internal.h new file mode 100644 index 0000000..9fef32f --- /dev/null +++ b/GexUnits/i2c/_i2c_internal.h @@ -0,0 +1,51 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_I2C_INTERNAL_H +#define GEX_F072_I2C_INTERNAL_H + +#ifndef I2C_INTERNAL +#error bad include! +#endif + +/** Private data structure */ +struct priv { + uint8_t periph_num; //!< 1 or 2 + uint8_t remap; //!< I2C remap option + + bool anf; //!< Enable analog noise filter + uint8_t dnf; //!< Enable digital noise filter (1-15 ... max spike width) + uint8_t speed; //!< 0 - Standard, 1 - Fast, 2 - Fast+ + + I2C_TypeDef *periph; +}; + +/** Load from a binary buffer stored in Flash */ +void UI2C_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UI2C_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UI2C_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UI2C_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Allocate data structure and set defaults */ +error_t UI2C_preInit(Unit *unit); + +/** Finalize unit set-up */ +error_t UI2C_init(Unit *unit); + +/** Tear down the unit */ +void UI2C_deInit(Unit *unit); + +// ------------------------------------------------------------------------ + +#endif //GEX_F072_I2C_INTERNAL_H diff --git a/GexUnits/i2c/_i2c_settings.c b/GexUnits/i2c/_i2c_settings.c new file mode 100644 index 0000000..79b3b1c --- /dev/null +++ b/GexUnits/i2c/_i2c_settings.c @@ -0,0 +1,103 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define I2C_INTERNAL +#include "_i2c_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UI2C_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->periph_num = pp_u8(pp); + priv->anf = pp_bool(pp); + priv->dnf = pp_u8(pp); + priv->speed = pp_u8(pp); + priv->remap = pp_u8(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void UI2C_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_u8(pb, priv->periph_num); + pb_bool(pb, priv->anf); + pb_u8(pb, priv->dnf); + pb_u8(pb, priv->speed); + pb_u8(pb, priv->remap); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UI2C_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "device")) { + priv->periph_num = cfg_u8_parse(value, &suc); + } + else if (streq(key, "remap")) { + priv->remap = cfg_u8_parse(value, &suc); + } + else if (streq(key, "analog-filter")) { + priv->anf = cfg_bool_parse(value, &suc); + } + else if (streq(key, "digital-filter")) { + priv->dnf = cfg_u8_parse(value, &suc); + } + else if (streq(key, "speed")) { + priv->speed = cfg_u8_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UI2C_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Peripheral number (I2Cx)"); + iw_entry_d(iw, "device", priv->periph_num); + + iw_comment(iw, "Pin mappings (SCL,SDA)"); +#if STM32F072xB + iw_comment(iw, " I2C1: (0) B6,B7 (1) B8,B9"); + iw_comment(iw, " I2C2: (0) B10,B11 (1) B13,B14"); +#elif GEX_PLAT_F103_BLUEPILL + #error "NO IMPL" +#elif GEX_PLAT_F303_DISCOVERY + #error "NO IMPL" +#elif GEX_PLAT_F407_DISCOVERY + #error "NO IMPL" +#else + #error "BAD PLATFORM!" +#endif + iw_entry_d(iw, "remap", priv->remap); + + iw_cmt_newline(iw); + iw_comment(iw, "Speed: 1-Standard, 2-Fast, 3-Fast+"); + iw_entry_d(iw, "speed", priv->speed); + + iw_comment(iw, "Analog noise filter enable (Y,N)"); + iw_entry_s(iw, "analog-filter", str_yn(priv->anf)); + + iw_comment(iw, "Digital noise filter bandwidth (0-15)"); + iw_entry_d(iw, "digital-filter", priv->dnf); +} diff --git a/GexUnits/i2c/unit_i2c.c b/GexUnits/i2c/unit_i2c.c new file mode 100644 index 0000000..d1d0402 --- /dev/null +++ b/GexUnits/i2c/unit_i2c.c @@ -0,0 +1,88 @@ +// +// Created by MightyPork on 2018/01/02. +// + +#include "comm/messages.h" +#include "unit_base.h" +#include "utils/avrlibc.h" +#include "unit_i2c.h" + +// I2C master +#define I2C_INTERNAL +#include "_i2c_internal.h" + +enum PinCmd_ { + CMD_WRITE = 0, + CMD_READ = 1, + CMD_WRITE_REG = 2, + CMD_READ_REG = 3, +}; + +/** Handle a request message */ +static error_t UI2C_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + uint16_t addr; + uint32_t len; + uint8_t regnum; + uint32_t size; + + // NOTE: 10-bit addresses must have the highest bit set to 1 for indication (0x8000 | addr) + + switch (command) { + /** Write byte(s) - addr:u16, byte(s) */ + case CMD_WRITE: + addr = pp_u16(pp); + const uint8_t *bb = pp_tail(pp, &len); + + return UU_I2C_Write(unit, addr, bb, len); + + /** Read byte(s) - addr:u16, len:u16 */ + case CMD_READ: + addr = pp_u16(pp); + len = pp_u16(pp); + + TRY(UU_I2C_Read(unit, addr, (uint8_t *) unit_tmp512, len)); + com_respond_buf(frame_id, MSG_SUCCESS, (uint8_t *) unit_tmp512, len); + return E_SUCCESS; + + /** Read register(s) - addr:u16, reg:u8, size:u16 */ + case CMD_READ_REG:; + addr = pp_u16(pp); + regnum = pp_u8(pp); // register number + size = pp_u16(pp); // total number of bytes to read (allows use of auto-increment) + + TRY(UU_I2C_ReadReg(unit, addr, regnum, (uint8_t *) unit_tmp512, size)); + com_respond_buf(frame_id, MSG_SUCCESS, (uint8_t *) unit_tmp512, size); + return E_SUCCESS; + + /** Write a register - addr:u16, reg:u8, byte(s) */ + case CMD_WRITE_REG: + addr = pp_u16(pp); + regnum = pp_u8(pp); // register number + const uint8_t *tail = pp_tail(pp, &size); + + return UU_I2C_WriteReg(unit, addr, regnum, tail, size); + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_I2C = { + .name = "I2C", + .description = "I2C master", + // Settings + .preInit = UI2C_preInit, + .cfgLoadBinary = UI2C_loadBinary, + .cfgWriteBinary = UI2C_writeBinary, + .cfgLoadIni = UI2C_loadIni, + .cfgWriteIni = UI2C_writeIni, + // Init + .init = UI2C_init, + .deInit = UI2C_deInit, + // Function + .handleRequest = UI2C_handleRequest, +}; diff --git a/GexUnits/i2c/unit_i2c.h b/GexUnits/i2c/unit_i2c.h new file mode 100644 index 0000000..c685aaa --- /dev/null +++ b/GexUnits/i2c/unit_i2c.h @@ -0,0 +1,76 @@ +// +// Created by MightyPork on 2018/01/02. +// +// I2C master unit +// + +#ifndef GEX_F072_UNIT_I2C_H +#define GEX_F072_UNIT_I2C_H + +#include "unit.h" + +extern const UnitDriver UNIT_I2C; + +// Unit-to-Unit API + +/** + * Raw write to I2C + * + * @param unit - I2C unit + * @param addr - device address (set highest bit if address is 10-bit) + * @param bytes - bytes to write + * @param bcount - byte count + * @return success + */ +error_t UU_I2C_Write(Unit *unit, uint16_t addr, const uint8_t *bytes, uint32_t bcount); + +/** + * Raw read from I2C + * + * @param unit - I2C unit + * @param addr - device address (set highest bit if address is 10-bit) + * @param dest - buffer for read bytes + * @param bcount - byte count + * @return success + */ +error_t UU_I2C_Read(Unit *unit, uint16_t addr, uint8_t *dest, uint32_t bcount); + +/** + * Read one or more registers from a I2C register-based device with auto-increment. + * + * @param unit - I2C unit + * @param addr - device address (set highest bit if address is 10-bit) + * @param regnum - first register number + * @param dest - destination buffer + * @param width - register width (or multiple consecutive registers total size) + * @return success + */ +error_t UU_I2C_ReadReg(Unit *unit, uint16_t addr, uint8_t regnum, uint8_t *dest, uint32_t width); + +/** + * Write a register value + * + * @param unit - I2C unit + * @param addr - device address (set highest bit if address is 10-bit) + * @param regnum - register number + * @param bytes - register bytes (use &byte) if just one + * @param width - register width (number of bytes) + * @return success + */ +error_t UU_I2C_WriteReg(Unit *unit, uint16_t addr, uint8_t regnum, const uint8_t *bytes, uint32_t width); + +/** + * Write a 8-bit register value + * + * @param unit - I2C unit + * @param addr - device address (set highest bit if address is 10-bit) + * @param regnum - register number + * @param value - byte to write to the register + * @return success + */ +static inline error_t UU_I2C_WriteReg8(Unit *unit, uint16_t addr, uint8_t regnum, uint8_t value) +{ + return UU_I2C_WriteReg(unit, addr, regnum, &value, 1); +} + +#endif //GEX_F072_UNIT_I2C_H diff --git a/GexUnits/neopixel/_npx_api.c b/GexUnits/neopixel/_npx_api.c new file mode 100644 index 0000000..2096e2e --- /dev/null +++ b/GexUnits/neopixel/_npx_api.c @@ -0,0 +1,53 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_neopixel.h" + +#define NPX_INTERNAL +#include "_npx_internal.h" +#include "ws2812.h" + +/* Clear the strip */ +error_t UU_Npx_Clear(Unit *unit) +{ + CHECK_TYPE(unit, &UNIT_NEOPIXEL); + + struct priv *priv = unit->data; + ws2812_clear(priv->port, priv->ll_pin, priv->cfg.pixels); + return E_SUCCESS; +} + +/* Load packed */ +error_t UU_Npx_Load(Unit *unit, const uint8_t *packed_rgb, uint32_t nbytes) +{ + CHECK_TYPE(unit, &UNIT_NEOPIXEL); + + struct priv *priv = unit->data; + if (nbytes != 3*priv->cfg.pixels) return E_BAD_COUNT; + ws2812_load_raw(priv->port, priv->ll_pin, packed_rgb, priv->cfg.pixels); + return E_SUCCESS; +} + +/* Load U32, LE or BE */ +error_t UU_Npx_Load32(Unit *unit, const uint8_t *bytes, uint32_t nbytes, bool order_bgr, bool zero_before) +{ + CHECK_TYPE(unit, &UNIT_NEOPIXEL); + + struct priv *priv = unit->data; + if (nbytes != 4*priv->cfg.pixels) return E_BAD_COUNT; + ws2812_load_sparse(priv->port, priv->ll_pin, bytes, priv->cfg.pixels, order_bgr, zero_before); + return E_SUCCESS; +} + +/* Get the pixel count */ +error_t UU_Npx_GetCount(Unit *unit, uint16_t *count) +{ + CHECK_TYPE(unit, &UNIT_NEOPIXEL); + + struct priv *priv = unit->data; + *count = priv->cfg.pixels; + return E_SUCCESS; +} diff --git a/GexUnits/neopixel/_npx_init.c b/GexUnits/neopixel/_npx_init.c new file mode 100644 index 0000000..8ca086d --- /dev/null +++ b/GexUnits/neopixel/_npx_init.c @@ -0,0 +1,58 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define NPX_INTERNAL +#include "_npx_internal.h" +#include "ws2812.h" + +/** Allocate data structure and set defaults */ +error_t Npx_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // some defaults + priv->cfg.pin = R_PA0; + priv->cfg.pixels = 1; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t Npx_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // --- Parse config --- + suc = hw_pinrsc2ll(priv->cfg.pin, &priv->port, &priv->ll_pin); + if (!suc) return E_BAD_CONFIG; + TRY(rsc_claim(unit, priv->cfg.pin)); + + // --- Init hardware --- + LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(priv->port, priv->ll_pin, LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinSpeed(priv->port, priv->ll_pin, LL_GPIO_SPEED_FREQ_HIGH); + + // clear strip + + ws2812_clear(priv->port, priv->ll_pin, priv->cfg.pixels); + + return E_SUCCESS; +} + +/** Tear down the unit */ +void Npx_deInit(Unit *unit) +{ + // pins are de-inited during teardown + + // Release all resources + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/neopixel/_npx_internal.h b/GexUnits/neopixel/_npx_internal.h new file mode 100644 index 0000000..827908a --- /dev/null +++ b/GexUnits/neopixel/_npx_internal.h @@ -0,0 +1,52 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_NPX_INTERNAL_H +#define GEX_F072_NPX_INTERNAL_H + +#ifndef NPX_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + struct { + Resource pin; + uint16_t pixels; + } cfg; + + uint32_t ll_pin; + GPIO_TypeDef *port; +}; + +// ------------------------------------------------------------------------ + +/** Allocate data structure and set defaults */ +error_t Npx_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void Npx_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void Npx_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t Npx_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void Npx_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t Npx_init(Unit *unit); + +/** Tear down the unit */ +void Npx_deInit(Unit *unit); + +#endif //GEX_F072_NPX_INTERNAL_H diff --git a/GexUnits/neopixel/_npx_settings.c b/GexUnits/neopixel/_npx_settings.c new file mode 100644 index 0000000..cf0045a --- /dev/null +++ b/GexUnits/neopixel/_npx_settings.c @@ -0,0 +1,61 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define NPX_INTERNAL +#include "_npx_internal.h" + +/** Load from a binary buffer stored in Flash */ +void Npx_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + priv->cfg.pin = (Resource) pp_u8(pp); + priv->cfg.pixels = pp_u16(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void Npx_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, priv->cfg.pin); + pb_u16(pb, priv->cfg.pixels); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t Npx_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "pin")) { + priv->cfg.pin = cfg_pinrsc_parse(value, &suc); + } + else if (streq(key, "pixels")) { + priv->cfg.pixels = cfg_u16_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void Npx_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Data pin"); + iw_entry_s(iw, "pin", cfg_pinrsc_encode(priv->cfg.pin)); + + iw_comment(iw, "Number of pixels"); + iw_entry_d(iw, "pixels", priv->cfg.pixels); +} diff --git a/GexUnits/neopixel/unit_neopixel.c b/GexUnits/neopixel/unit_neopixel.c new file mode 100644 index 0000000..e8ccef8 --- /dev/null +++ b/GexUnits/neopixel/unit_neopixel.c @@ -0,0 +1,76 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_neopixel.h" + +#define NPX_INTERNAL +#include "_npx_internal.h" + +enum PinCmd_ { + CMD_CLEAR = 0, + CMD_LOAD = 1, + CMD_LOAD_ZRGB = 4, // 0,0 - trail zero, bgr + CMD_LOAD_ZBGR = 5, // 0,1 + CMD_LOAD_RGBZ = 6, // 1,0 + CMD_LOAD_BGRZ = 7, // 1,1 + CMD_GET_LEN = 10, +}; + +/** Handle a request message */ +static error_t Npx_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + uint32_t len; + const uint8_t *bytes; + + switch (command) { + /** Clear the entire strip */ + case CMD_CLEAR: + return UU_Npx_Clear(unit); + + /** Load packed RGB colors (length must match the strip size) */ + case CMD_LOAD:; + bytes = pp_tail(pp, &len); + return UU_Npx_Load(unit, bytes, len); + + /** Load sparse (uint32_t) colors */ + case CMD_LOAD_ZRGB: + case CMD_LOAD_ZBGR: + case CMD_LOAD_RGBZ: + case CMD_LOAD_BGRZ: + bytes = pp_tail(pp, &len); + bool trail_zero = (bool) (command & 0b10); + bool order_bgr = (bool) (command & 0b01); + return UU_Npx_Load32(unit, bytes, len, order_bgr, !trail_zero); + + /** Get the Neopixel strip length */ + case CMD_GET_LEN:; + uint16_t count; + TRY(UU_Npx_GetCount(unit, &count)); + com_respond_u16(frame_id, count); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_NEOPIXEL = { + .name = "NPX", + .description = "Neopixel RGB LED strip", + // Settings + .preInit = Npx_preInit, + .cfgLoadBinary = Npx_loadBinary, + .cfgWriteBinary = Npx_writeBinary, + .cfgLoadIni = Npx_loadIni, + .cfgWriteIni = Npx_writeIni, + // Init + .init = Npx_init, + .deInit = Npx_deInit, + // Function + .handleRequest = Npx_handleRequest, +}; diff --git a/GexUnits/neopixel/unit_neopixel.h b/GexUnits/neopixel/unit_neopixel.h new file mode 100644 index 0000000..69d6aee --- /dev/null +++ b/GexUnits/neopixel/unit_neopixel.h @@ -0,0 +1,53 @@ +// +// Created by MightyPork on 2017/11/25. +// +// NeoPixel RGB LED strip bit-banged output. +// The nanosecond timing is derived from the AHB clock speed. +// + +#ifndef U_NEOPIXEL_H +#define U_NEOPIXEL_H + +#include "unit.h" + +extern const UnitDriver UNIT_NEOPIXEL; + +/** + * Clear the Neopixel strip + * + * @param unit + * @return success + */ +error_t UU_Npx_Clear(Unit *unit); + +/** + * Load the strip with packed bytes R,G,B. + * + * @param unit + * @param packed_rgb - bytes to load + * @param nbytes - number of bytes, must be count*3 + * @return success + */ +error_t UU_Npx_Load(Unit *unit, const uint8_t *packed_rgb, uint32_t nbytes); + +/** + * Load from 32-bit numbers + * @param unit + * @param bytes + * @param nbytes + * @param order_bgr + * @param zero_before + * @return success + */ +error_t UU_Npx_Load32(Unit *unit, const uint8_t *bytes, uint32_t nbytes, bool order_bgr, bool zero_before); + +/** + * Get number of pixels on the strip + * + * @param unit + * @param count - destination for the count value + * @return success + */ +error_t UU_Npx_GetCount(Unit *unit, uint16_t *count); + +#endif //U_NEOPIXEL_H diff --git a/GexUnits/neopixel/ws2812.c b/GexUnits/neopixel/ws2812.c new file mode 100644 index 0000000..9ec090b --- /dev/null +++ b/GexUnits/neopixel/ws2812.c @@ -0,0 +1,83 @@ +#include "platform.h" +#include "ws2812.h" + +#define FREQ_STEP (PLAT_AHB_MHZ/20.0f) +#define NPX_DELAY_SHORT (uint32_t)(FREQ_STEP*1.5f) +#define NPX_DELAY_LONG (uint32_t)(FREQ_STEP*3.5f) +#define NPX_DELAY_SHOW (uint32_t)(FREQ_STEP*50) + +static inline __attribute__((always_inline)) +void ws2812_byte(GPIO_TypeDef *port, uint32_t ll_pin, uint8_t b) +{ + for (register volatile uint8_t i = 0; i < 8; i++) { + LL_GPIO_SetOutputPin(port, ll_pin); + + // duty cycle determines bit value + if (b & 0x80) { + __asm_loop(NPX_DELAY_LONG); + LL_GPIO_ResetOutputPin(port, ll_pin); + __asm_loop(NPX_DELAY_SHORT); + } else { + __asm_loop(NPX_DELAY_SHORT); + LL_GPIO_ResetOutputPin(port, ll_pin); + __asm_loop(NPX_DELAY_LONG); + } + + b <<= 1; // shift to next bit + } +} + +/** Set many RGBs from packed stream */ +void ws2812_load_raw(GPIO_TypeDef *port, uint32_t ll_pin, const uint8_t *rgbs, uint32_t count) +{ + vPortEnterCritical(); + uint8_t b, g, r; + for (uint32_t i = 0; i < count; i++) { + r = *rgbs++; + g = *rgbs++; + b = *rgbs++; + ws2812_byte(port, ll_pin, g); + ws2812_byte(port, ll_pin, r); + ws2812_byte(port, ll_pin, b); + } + vPortExitCritical(); + __asm_loop(NPX_DELAY_SHOW); +} + +/** Set many RGBs from uint32 stream */ +void ws2812_load_sparse(GPIO_TypeDef *port, uint32_t ll_pin, const uint8_t *rgbs, uint32_t count, + bool order_bgr, bool zero_before) +{ + vPortEnterCritical(); + uint8_t b, g, r; + for (uint32_t i = 0; i < count; i++) { + if (zero_before) rgbs++; // skip + if (order_bgr) { + b = *rgbs++; + g = *rgbs++; + r = *rgbs++; + } else { + r = *rgbs++; + g = *rgbs++; + b = *rgbs++; + } + if (!zero_before) rgbs++; // skip + + ws2812_byte(port, ll_pin, g); + ws2812_byte(port, ll_pin, r); + ws2812_byte(port, ll_pin, b); + } + vPortExitCritical(); + __asm_loop(NPX_DELAY_SHOW); +} + +/** Set many RGBs */ +void ws2812_clear(GPIO_TypeDef *port, uint32_t ll_pin, uint32_t count) +{ + vPortEnterCritical(); + for (uint32_t i = 0; i < count*3; i++) { + ws2812_byte(port, ll_pin, 0); + } + vPortExitCritical(); + __asm_loop(NPX_DELAY_SHOW); +} diff --git a/GexUnits/neopixel/ws2812.h b/GexUnits/neopixel/ws2812.h new file mode 100644 index 0000000..cf68145 --- /dev/null +++ b/GexUnits/neopixel/ws2812.h @@ -0,0 +1,37 @@ +#ifndef WS2812_H +#define WS2812_H + +#include "platform.h" + +/** + * Load RGBs from a packed byte stream + * + * @param port + * @param ll_pin + * @param rgbs - packed R,G,B, R,G,B, ... array + * @param count - number of pixels (triplets) + */ +void ws2812_load_raw(GPIO_TypeDef *port, uint32_t ll_pin, const uint8_t *rgbs, uint32_t count); + +/** + * Load all pixels with BLACK (0,0,0) + * + * @param port + * @param ll_pin + * @param count - number of pixels + */ +void ws2812_clear(GPIO_TypeDef *port, uint32_t ll_pin, uint32_t count); + +/** + * Load from a stream of 32-bit numbers (4th or 1st byte skipped) + * @param port + * @param ll_pin + * @param rgbs - payload + * @param count - number of pixels + * @param order_bgr - B,G,R colors, false - R,G,B + * @param zero_before - insert padding byte before colors, false - after + */ +void ws2812_load_sparse(GPIO_TypeDef *port, uint32_t ll_pin, const uint8_t *rgbs, uint32_t count, + bool order_bgr, bool zero_before); + +#endif //WS2812_H diff --git a/GexUnits/pwmdim/_pwmdim_api.c b/GexUnits/pwmdim/_pwmdim_api.c new file mode 100644 index 0000000..ef5c920 --- /dev/null +++ b/GexUnits/pwmdim/_pwmdim_api.c @@ -0,0 +1,64 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_pwmdim.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +error_t UPWMDIM_SetFreq(Unit *unit, uint32_t freq) +{ + struct priv *priv = unit->data; + + uint16_t presc; + uint32_t count; + float real_freq; + if (!hw_solve_timer(PLAT_APB1_HZ, freq, true, &presc, &count, &real_freq)) { + dbg("Failed to resolve timer params."); + return E_BAD_VALUE; + } + LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); + LL_TIM_SetAutoReload(priv->TIMx, count - 1); + + // we must re-calculate duty cycles because they are absolute related to the ARR which we just changed + UPWMDIM_SetDuty(unit, 0, priv->duty1); + UPWMDIM_SetDuty(unit, 1, priv->duty2); + UPWMDIM_SetDuty(unit, 2, priv->duty3); + UPWMDIM_SetDuty(unit, 3, priv->duty4); + +// LL_TIM_GenerateEvent_UPDATE(priv->TIMx); // - this appears to cause jumpiness + priv->freq = freq; + + return E_SUCCESS; +} + +error_t UPWMDIM_SetDuty(Unit *unit, uint8_t ch, uint16_t duty1000) +{ + struct priv *priv = unit->data; + + uint32_t cnt = (LL_TIM_GetAutoReload(priv->TIMx) + 1)*duty1000 / 1000; + + if (ch == 0) { + priv->duty1 = duty1000; + LL_TIM_OC_SetCompareCH1(priv->TIMx, cnt); + } + else if (ch == 1) { + priv->duty2 = duty1000; + LL_TIM_OC_SetCompareCH2(priv->TIMx, cnt); + } + else if (ch == 2) { + priv->duty3 = duty1000; + LL_TIM_OC_SetCompareCH3(priv->TIMx, cnt); + } + else if (ch == 3) { + priv->duty4 = duty1000; + LL_TIM_OC_SetCompareCH4(priv->TIMx, cnt); + } else { + return E_BAD_VALUE; + } + + return E_SUCCESS; +} diff --git a/GexUnits/pwmdim/_pwmdim_init.c b/GexUnits/pwmdim/_pwmdim_init.c new file mode 100644 index 0000000..739b1fe --- /dev/null +++ b/GexUnits/pwmdim/_pwmdim_init.c @@ -0,0 +1,170 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +/** Allocate data structure and set defaults */ +error_t UPWMDIM_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + priv->cfg.freq = 1000; + priv->cfg.ch1_choice = 1; + priv->cfg.ch2_choice = 0; + priv->cfg.ch3_choice = 0; + priv->cfg.ch4_choice = 0; + + priv->duty1 = 500; + priv->duty2 = 500; + priv->duty3 = 500; + priv->duty4 = 500; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UPWMDIM_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + TRY(rsc_claim(unit, R_TIM3)); + priv->TIMx = TIM3; + hw_periph_clock_enable(priv->TIMx); + + // copy the default frequency + priv->freq = priv->cfg.freq; + + const Resource ch1_pins[] = { R_PA6, R_PB4, R_PC6 }; + const uint32_t ch1_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + const Resource ch2_pins[] = { R_PA7, R_PB5, R_PC7 }; + const uint32_t ch2_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + const Resource ch3_pins[] = { R_PB0, R_PC8 }; + const uint32_t ch3_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + const Resource ch4_pins[] = { R_PB1, R_PC9 }; + const uint32_t ch4_af[] = { LL_GPIO_AF_1, LL_GPIO_AF_0 }; + + Resource r[4] = {}; + uint32_t af[4] = {}; + + // --- resolve pins and AFs --- + if (priv->cfg.ch1_choice > 0) { + if (priv->cfg.ch1_choice > 3) return E_BAD_CONFIG; + r[0] = ch1_pins[priv->cfg.ch1_choice - 1]; + af[0] = ch1_af[priv->cfg.ch1_choice - 1]; + TRY(rsc_claim(unit, r[0])); + } + + if (priv->cfg.ch2_choice > 0) { + if (priv->cfg.ch2_choice > 3) return E_BAD_CONFIG; + r[1] = ch2_pins[priv->cfg.ch2_choice - 1]; + af[1] = ch2_af[priv->cfg.ch2_choice - 1]; + TRY(rsc_claim(unit, r[1])); + } + + if (priv->cfg.ch3_choice > 0) { + if (priv->cfg.ch3_choice > 2) return E_BAD_CONFIG; + r[2] = ch3_pins[priv->cfg.ch3_choice - 1]; + af[2] = ch3_af[priv->cfg.ch3_choice - 1]; + TRY(rsc_claim(unit, r[2])); + } + + if (priv->cfg.ch4_choice > 0) { + if (priv->cfg.ch4_choice > 2) return E_BAD_CONFIG; + r[3] = ch4_pins[priv->cfg.ch4_choice - 1]; + af[3] = ch4_af[priv->cfg.ch4_choice - 1]; + TRY(rsc_claim(unit, r[3])); + } + + // --- configure AF + timer --- + LL_TIM_DeInit(priv->TIMx); // force a reset + + uint16_t presc; + uint32_t count; + float real_freq; + if (!hw_solve_timer(PLAT_APB1_HZ, priv->freq, true, &presc, &count, &real_freq)) { + dbg("Failed to resolve timer params."); + return E_BAD_VALUE; + } + LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); + LL_TIM_SetAutoReload(priv->TIMx, count - 1); + LL_TIM_EnableARRPreload(priv->TIMx); + + dbg("Presc %d, cnt %d", (int)presc, (int)count); + + // TODO this can probably be turned into a loop over an array of structs + + + if (priv->cfg.ch1_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[0], af[0])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH1); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH1(priv->TIMx, count/2); + LL_TIM_CC_EnablePreload(priv->TIMx); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH1); + } + + if (priv->cfg.ch2_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[1], af[1])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH2); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH2(priv->TIMx, count/2); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH2); + } + + if (priv->cfg.ch3_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[2], af[2])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH3); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH3(priv->TIMx, count/2); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH3); + } + + if (priv->cfg.ch4_choice > 0) { + TRY(hw_configure_gpiorsc_af(r[3], af[3])); + + LL_TIM_OC_EnablePreload(priv->TIMx, LL_TIM_CHANNEL_CH4); + LL_TIM_OC_SetMode(priv->TIMx, LL_TIM_CHANNEL_CH4, LL_TIM_OCMODE_PWM1); + LL_TIM_OC_SetCompareCH4(priv->TIMx, count/2); + LL_TIM_CC_EnableChannel(priv->TIMx, LL_TIM_CHANNEL_CH4); + } + + LL_TIM_GenerateEvent_UPDATE(priv->TIMx); + LL_TIM_EnableAllOutputs(priv->TIMx); + + // postpone this for later - when user uses the start command. + // prevents beeping right after restart if used for audio. +// LL_TIM_EnableCounter(priv->TIMx); + + return E_SUCCESS; +} + + +/** Tear down the unit */ +void UPWMDIM_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS ) { + LL_TIM_DeInit(priv->TIMx); + } + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/pwmdim/_pwmdim_internal.h b/GexUnits/pwmdim/_pwmdim_internal.h new file mode 100644 index 0000000..f456fe8 --- /dev/null +++ b/GexUnits/pwmdim/_pwmdim_internal.h @@ -0,0 +1,65 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_PWMDIM_INTERNAL_H +#define GEX_F072_PWMDIM_INTERNAL_H + +#ifndef PWMDIM_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + // settings + struct { + uint32_t freq; + uint8_t ch1_choice; + uint8_t ch2_choice; + uint8_t ch3_choice; + uint8_t ch4_choice; + } cfg; + + // internal state + uint32_t freq; + uint16_t duty1; + uint16_t duty2; + uint16_t duty3; + uint16_t duty4; + + TIM_TypeDef *TIMx; +}; + +/** Allocate data structure and set defaults */ +error_t UPWMDIM_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UPWMDIM_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UPWMDIM_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UPWMDIM_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UPWMDIM_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UPWMDIM_init(Unit *unit); + +/** Tear down the unit */ +void UPWMDIM_deInit(Unit *unit); + + +error_t UPWMDIM_SetFreq(Unit *unit, uint32_t freq); + +error_t UPWMDIM_SetDuty(Unit *unit, uint8_t ch, uint16_t duty1000); + +#endif //GEX_F072_PWMDIM_INTERNAL_H diff --git a/GexUnits/pwmdim/_pwmdim_settings.c b/GexUnits/pwmdim/_pwmdim_settings.c new file mode 100644 index 0000000..7de7567 --- /dev/null +++ b/GexUnits/pwmdim/_pwmdim_settings.c @@ -0,0 +1,89 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UPWMDIM_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->cfg.freq = pp_u32(pp); + priv->cfg.ch1_choice = pp_u8(pp); + priv->cfg.ch2_choice = pp_u8(pp); + priv->cfg.ch3_choice = pp_u8(pp); + priv->cfg.ch4_choice = pp_u8(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void UPWMDIM_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_u32(pb, priv->cfg.freq); + pb_u8(pb, priv->cfg.ch1_choice); + pb_u8(pb, priv->cfg.ch2_choice); + pb_u8(pb, priv->cfg.ch3_choice); + pb_u8(pb, priv->cfg.ch4_choice); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UPWMDIM_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "frequency")) { + priv->cfg.freq = cfg_u32_parse(value, &suc); + } + else if (streq(key, "ch1_pin")) { + priv->cfg.ch1_choice = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ch2_pin")) { + priv->cfg.ch2_choice = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ch3_pin")) { + priv->cfg.ch3_choice = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ch4_pin")) { + priv->cfg.ch4_choice = cfg_u8_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UPWMDIM_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Default pulse frequency (Hz)"); + iw_entry_d(iw, "frequency", priv->cfg.freq); + + iw_comment(iw, "Pin mapping - 0=disabled"); + iw_comment(iw, "Channel1 - 1:PA6, 2:PB4, 3:PC6"); + iw_entry_d(iw, "ch1_pin", priv->cfg.ch1_choice); + iw_comment(iw, "Channel2 - 1:PA7, 2:PB5, 3:PC7"); + iw_entry_d(iw, "ch2_pin", priv->cfg.ch2_choice); + iw_comment(iw, "Channel3 - 1:PB0, 2:PC8"); + iw_entry_d(iw, "ch3_pin", priv->cfg.ch3_choice); + iw_comment(iw, "Channel4 - 1:PB1, 2:PC9"); + iw_entry_d(iw, "ch4_pin", priv->cfg.ch4_choice); +} + diff --git a/GexUnits/pwmdim/unit_pwmdim.c b/GexUnits/pwmdim/unit_pwmdim.c new file mode 100644 index 0000000..dac34e7 --- /dev/null +++ b/GexUnits/pwmdim/unit_pwmdim.c @@ -0,0 +1,69 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_pwmdim.h" + +#define PWMDIM_INTERNAL +#include "_pwmdim_internal.h" + +// ------------------------------------------------------------------------ + +enum PwmSimpleCmd_ { + CMD_SET_FREQUENCY = 0, + CMD_SET_DUTY = 1, + CMD_STOP = 2, + CMD_START = 3, +}; + +/** Handle a request message */ +static error_t UPWMDIM_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + switch (command) { + case CMD_SET_FREQUENCY: + TRY(UPWMDIM_SetFreq(unit, pp_u32(pp))); + return E_SUCCESS; + + case CMD_SET_DUTY: + for (; pp_length(pp) > 0;) { + uint8_t ch = pp_u8(pp); + uint16_t duty = pp_u16(pp); + TRY(UPWMDIM_SetDuty(unit, ch, duty)); + } + return E_SUCCESS; + + case CMD_STOP: + LL_TIM_DisableCounter(priv->TIMx); + LL_TIM_SetCounter(priv->TIMx, 0); + return E_SUCCESS; + + case CMD_START: + LL_TIM_EnableCounter(priv->TIMx); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Simple PWM dimming output */ +const UnitDriver UNIT_PWMDIM = { + .name = "PWMDIM", + .description = "Simple PWM output", + // Settings + .preInit = UPWMDIM_preInit, + .cfgLoadBinary = UPWMDIM_loadBinary, + .cfgWriteBinary = UPWMDIM_writeBinary, + .cfgLoadIni = UPWMDIM_loadIni, + .cfgWriteIni = UPWMDIM_writeIni, + // Init + .init = UPWMDIM_init, + .deInit = UPWMDIM_deInit, + // Function + .handleRequest = UPWMDIM_handleRequest, +}; diff --git a/GexUnits/pwmdim/unit_pwmdim.h b/GexUnits/pwmdim/unit_pwmdim.h new file mode 100644 index 0000000..220c430 --- /dev/null +++ b/GexUnits/pwmdim/unit_pwmdim.h @@ -0,0 +1,16 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital input unit; single or multiple pin read access on one port (A-F) +// + +#ifndef U_PWMDIM_H +#define U_PWMDIM_H + +#include "unit.h" + +extern const UnitDriver UNIT_PWMDIM; + +// UU_ prototypes + +#endif //U_PWMDIM_H diff --git a/GexUnits/sipo/_sipo_api.c b/GexUnits/sipo/_sipo_api.c new file mode 100644 index 0000000..3031186 --- /dev/null +++ b/GexUnits/sipo/_sipo_api.c @@ -0,0 +1,110 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_sipo.h" + +#define SIPO_INTERNAL +#include "_sipo_internal.h" + +static void send_pulse(bool pol, GPIO_TypeDef *port, uint32_t ll) +{ + if (pol) { + LL_GPIO_SetOutputPin(port, ll); + } + else { + LL_GPIO_ResetOutputPin(port, ll); + } + + __asm_loop(2); + + if (pol) { + LL_GPIO_ResetOutputPin(port, ll); + } + else { + LL_GPIO_SetOutputPin(port, ll); + } +} + +#pragma GCC push_options +#pragma GCC optimize ("O2") +error_t UU_SIPO_Write(Unit *unit, const uint8_t *buffer, uint16_t buflen, uint16_t terminal_data) +{ + CHECK_TYPE(unit, &UNIT_SIPO); + struct priv *priv = unit->data; + + if (buflen % priv->data_width != 0) { + dbg("Buflen %d vs width %d", (int)buflen, (int)priv->data_width); + return E_BAD_COUNT; // must be a multiple of the channel count + } + + // buffer contains data for the individual data pins, back to back as AAA BBB CCC (whole bytes) + const uint8_t data_width = priv->data_width; + const uint16_t bytelen = buflen / data_width; + const uint16_t mask = priv->cfg.data_pins; + + uint8_t offsets[16]; + for (int i=0; i<16; i++) offsets[i] = (uint8_t) (bytelen * i); + + for (int32_t bn = bytelen - 1; bn >= 0; bn--) { + // send the byte + for (int32_t i = 0; i < 8; i++) { + uint16_t packed = 0; + for (int32_t j = data_width - 1; j >= 0; j--) { + packed |= (buffer[bn + offsets[j]] >> i) & 1; + if (j > 0) packed <<= 1; + } + + uint16_t spread = pinmask_spread(packed, mask); + priv->data_port->BSRR = spread | (((~spread) & mask) << 16); + + // Shift clock pulse + send_pulse(priv->cfg.shift_pol, priv->shift_port, priv->shift_ll); + } + } + + // load the final data - this may be used by some other circuitry or + // simply to rest the lines at a defined known level + uint16_t spread = pinmask_spread(terminal_data, mask); + priv->data_port->BSRR = spread | (((~spread) & mask) << 16); + + send_pulse(priv->cfg.store_pol, priv->store_port, priv->store_ll); + return E_SUCCESS; +} +#pragma GCC pop_options + +error_t UU_SIPO_DirectData(Unit *unit, uint16_t data_packed) +{ + CHECK_TYPE(unit, &UNIT_SIPO); + struct priv *priv = unit->data; + + uint16_t spread = pinmask_spread(data_packed, priv->cfg.data_pins); + priv->data_port->BSRR = spread | (((~spread) & priv->cfg.data_pins) << 16); + return E_SUCCESS; +} + +error_t UU_SIPO_DirectClear(Unit *unit) +{ + CHECK_TYPE(unit, &UNIT_SIPO); + struct priv *priv = unit->data; + send_pulse(priv->cfg.clear_pol, priv->clear_port, priv->clear_ll); + return E_SUCCESS; +} + +error_t UU_SIPO_DirectShift(Unit *unit) +{ + CHECK_TYPE(unit, &UNIT_SIPO); + struct priv *priv = unit->data; + send_pulse(priv->cfg.shift_pol, priv->shift_port, priv->shift_ll); + return E_SUCCESS; +} + +error_t UU_SIPO_DirectStore(Unit *unit) +{ + CHECK_TYPE(unit, &UNIT_SIPO); + struct priv *priv = unit->data; + send_pulse(priv->cfg.store_pol, priv->store_port, priv->store_ll); + return E_SUCCESS; +} diff --git a/GexUnits/sipo/_sipo_init.c b/GexUnits/sipo/_sipo_init.c new file mode 100644 index 0000000..4ab6427 --- /dev/null +++ b/GexUnits/sipo/_sipo_init.c @@ -0,0 +1,113 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define SIPO_INTERNAL +#include "_sipo_internal.h" + +/** Allocate data structure and set defaults */ +error_t USIPO_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + priv->cfg.pin_store = R_PA0; + priv->cfg.store_pol = true; + + priv->cfg.pin_shift = R_PA1; + priv->cfg.shift_pol = true; + + priv->cfg.pin_clear = R_PA2; + priv->cfg.clear_pol = false; + + priv->cfg.data_pname = 'A'; + priv->cfg.data_pins = (1<<3); + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t USIPO_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // --- Parse config --- + suc &= hw_pinrsc2ll(priv->cfg.pin_store, &priv->store_port, &priv->store_ll); + suc &= hw_pinrsc2ll(priv->cfg.pin_shift, &priv->shift_port, &priv->shift_ll); + suc &= hw_pinrsc2ll(priv->cfg.pin_clear, &priv->clear_port, &priv->clear_ll); + if (!suc) return E_BAD_CONFIG; + TRY(rsc_claim(unit, priv->cfg.pin_store)); + TRY(rsc_claim(unit, priv->cfg.pin_shift)); + TRY(rsc_claim(unit, priv->cfg.pin_clear)); + + // Claim all needed pins + TRY(rsc_claim_gpios(unit, priv->cfg.data_pname, priv->cfg.data_pins)); + priv->data_port = hw_port2periph(priv->cfg.data_pname, &suc); + + // --- Init hardware --- + + priv->data_width = 0; + for (int i = 0; i < 16; i++) { + if (priv->cfg.data_pins & (1 << i)) { + uint32_t ll_pin = hw_pin2ll((uint8_t) i, &suc); + LL_GPIO_SetPinMode(priv->data_port, ll_pin, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(priv->data_port, ll_pin, LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinSpeed(priv->data_port, ll_pin, LL_GPIO_SPEED_FREQ_HIGH); + priv->data_width++; + } + } + + // Set the initial state - zeros + priv->data_port->ODR &= ~priv->cfg.data_pins; + + + // STORE + LL_GPIO_SetPinMode(priv->store_port, priv->store_ll, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(priv->store_port, priv->store_ll, LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinSpeed(priv->store_port, priv->store_ll, LL_GPIO_SPEED_FREQ_HIGH); + + if (priv->cfg.store_pol) + LL_GPIO_ResetOutputPin(priv->store_port, priv->store_ll); + else + LL_GPIO_SetOutputPin(priv->store_port, priv->store_ll); + + // SHIFT + LL_GPIO_SetPinMode(priv->shift_port, priv->shift_ll, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(priv->shift_port, priv->shift_ll, LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinSpeed(priv->shift_port, priv->shift_ll, LL_GPIO_SPEED_FREQ_HIGH); + + if (priv->cfg.shift_pol) + LL_GPIO_ResetOutputPin(priv->shift_port, priv->shift_ll); + else + LL_GPIO_SetOutputPin(priv->shift_port, priv->shift_ll); + + // CLEAR + LL_GPIO_SetPinMode(priv->clear_port, priv->clear_ll, LL_GPIO_MODE_OUTPUT); + LL_GPIO_SetPinOutputType(priv->clear_port, priv->clear_ll, LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinSpeed(priv->clear_port, priv->clear_ll, LL_GPIO_SPEED_FREQ_HIGH); + + if (priv->cfg.clear_pol) + LL_GPIO_ResetOutputPin(priv->clear_port, priv->clear_ll); + else + LL_GPIO_SetOutputPin(priv->clear_port, priv->clear_ll); + + // initial clear + UU_SIPO_DirectClear(unit); + + return E_SUCCESS; +} + + +/** Tear down the unit */ +void USIPO_deInit(Unit *unit) +{ + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/sipo/_sipo_internal.h b/GexUnits/sipo/_sipo_internal.h new file mode 100644 index 0000000..15ba581 --- /dev/null +++ b/GexUnits/sipo/_sipo_internal.h @@ -0,0 +1,120 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_SIPO_INTERNAL_H +#define GEX_F072_SIPO_INTERNAL_H + +#ifndef SIPO_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + struct { + // settings + Resource pin_store; + bool store_pol; //!< Store pulse active edge + + Resource pin_shift; + bool shift_pol; //!< Shift clock active edge + + Resource pin_clear; + bool clear_pol; //!< Clear signal active level + + char data_pname; + uint16_t data_pins; + } cfg; + + // live fields + uint32_t store_ll; + uint32_t shift_ll; + uint32_t clear_ll; + + GPIO_TypeDef *store_port; + GPIO_TypeDef *shift_port; + GPIO_TypeDef *clear_port; + GPIO_TypeDef *data_port; + + uint8_t data_width; +}; + +/** Allocate data structure and set defaults */ +error_t USIPO_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void USIPO_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void USIPO_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t USIPO_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void USIPO_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t USIPO_init(Unit *unit); + +/** Tear down the unit */ +void USIPO_deInit(Unit *unit); + +// ------------------------------------------------------------------------ + +/** + * Write a buffer to the pins. + * Buffer contains data for the individual channels, sequentially (AAAAAA BBBBBB CCCCCC ...) + * The bytes are sent LSB first, from the last byte (e.g. 1,2,3 - 3 is sent first, LSB-first). + * + * The chunks order is from the lowest to the highest bit + * + * @param unit + * @param buffer - buffer of data to send + * @param buflen - number of bytes in the buffer + * @param terminal_data - data to set before sending the store pulse (final data lines state, will not appear in the SIPOs) + * @return success + */ +error_t UU_SIPO_Write(Unit *unit, const uint8_t *buffer, uint16_t buflen, uint16_t terminal_data); + +/** + * Direct access to the output data pins (may be useful for debugging, or circuits that use them + * for something else when not loading a new value). + * + * @param unit + * @param data_packed - packed data to set on the output (right-aligned, highest to lowest pin) + * @return success + */ +error_t UU_SIPO_DirectData(Unit *unit, uint16_t data_packed); + +/** + * Send a clear pulse. + * + * @param unit + * @return success + */ +error_t UU_SIPO_DirectClear(Unit *unit); + +/** + * Send a shift pulse. + * + * @param unit + * @return success + */ +error_t UU_SIPO_DirectShift(Unit *unit); + +/** + * Send a store pulse. + * + * @param unit + * @return success + */ +error_t UU_SIPO_DirectStore(Unit *unit); + +#endif //GEX_F072_SIPO_INTERNAL_H diff --git a/GexUnits/sipo/_sipo_settings.c b/GexUnits/sipo/_sipo_settings.c new file mode 100644 index 0000000..c915705 --- /dev/null +++ b/GexUnits/sipo/_sipo_settings.c @@ -0,0 +1,116 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define SIPO_INTERNAL +#include "_sipo_internal.h" + +/** Load from a binary buffer stored in Flash */ +void USIPO_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->cfg.pin_store = (Resource) pp_u8(pp); + priv->cfg.store_pol = pp_bool(pp); + + priv->cfg.pin_shift = (Resource) pp_u8(pp); + priv->cfg.shift_pol = pp_bool(pp); + + priv->cfg.pin_clear = (Resource) pp_u8(pp); + priv->cfg.clear_pol = pp_bool(pp); + + priv->cfg.data_pname = pp_char(pp); + priv->cfg.data_pins = pp_u16(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void USIPO_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_u8(pb, priv->cfg.pin_store); + pb_bool(pb, priv->cfg.store_pol); + + pb_u8(pb, priv->cfg.pin_shift); + pb_bool(pb, priv->cfg.shift_pol); + + pb_u8(pb, priv->cfg.pin_clear); + pb_bool(pb, priv->cfg.clear_pol); + + pb_char(pb, priv->cfg.data_pname); + pb_u16(pb, priv->cfg.data_pins); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t USIPO_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "store-pin")) { + priv->cfg.pin_store = cfg_pinrsc_parse(value, &suc); + } + else if (streq(key, "shift-pin")) { + priv->cfg.pin_shift = cfg_pinrsc_parse(value, &suc); + } + else if (streq(key, "clear-pin")) { + priv->cfg.pin_clear = cfg_pinrsc_parse(value, &suc); + } + + else if (streq(key, "store-pol")) { + priv->cfg.store_pol = cfg_bool_parse(value, &suc); + } + else if (streq(key, "shift-pol")) { + priv->cfg.shift_pol = cfg_bool_parse(value, &suc); + } + else if (streq(key, "clear-pol")) { + priv->cfg.clear_pol = cfg_bool_parse(value, &suc); + } + + else if (streq(key, "data-port")) { + suc = cfg_port_parse(value, &priv->cfg.data_pname); + } + else if (streq(key, "data-pins")) { + priv->cfg.data_pins = cfg_pinmask_parse(value, &suc); + } + + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void USIPO_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Shift pin & its active edge (1-rising,0-falling)"); + iw_entry_s(iw, "shift-pin", cfg_pinrsc_encode(priv->cfg.pin_shift)); + iw_entry_d(iw, "shift-pol", priv->cfg.shift_pol); + + iw_comment(iw, "Store pin & its active edge"); + iw_entry_s(iw, "store-pin", cfg_pinrsc_encode(priv->cfg.pin_store)); + iw_entry_d(iw, "store-pol", priv->cfg.store_pol); + + iw_comment(iw, "Clear pin & its active level"); + iw_entry_s(iw, "clear-pin", cfg_pinrsc_encode(priv->cfg.pin_clear)); + iw_entry_d(iw, "clear-pol", priv->cfg.clear_pol); + + iw_comment(iw, "Data port and pins"); + iw_entry(iw, "data-port", "%c", priv->cfg.data_pname); + iw_entry_s(iw, "data-pins", cfg_pinmask_encode(priv->cfg.data_pins, unit_tmp512, true)); +} + diff --git a/GexUnits/sipo/unit_sipo.c b/GexUnits/sipo/unit_sipo.c new file mode 100644 index 0000000..d48cb1b --- /dev/null +++ b/GexUnits/sipo/unit_sipo.c @@ -0,0 +1,73 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_sipo.h" + +#define SIPO_INTERNAL + +#include "_sipo_internal.h" + +// ------------------------------------------------------------------------ + +enum SipoCmd_ { + CMD_WRITE = 0, + CMD_DIRECT_DATA = 1, + CMD_DIRECT_SHIFT = 2, + CMD_DIRECT_CLEAR = 3, + CMD_DIRECT_STORE = 4, +}; + +/** Handle a request message */ +static error_t USIPO_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + switch (command) { + case CMD_WRITE: + { + uint32_t len; + uint16_t terminal_packed = pp_u16(pp); + const uint8_t *tail = pp_tail(pp, &len); + TRY(UU_SIPO_Write(unit, (uint8_t *) tail, (uint16_t) len, terminal_packed)); + } + return E_SUCCESS; + + case CMD_DIRECT_DATA: + TRY(UU_SIPO_DirectData(unit, pp_u16(pp))); + return E_SUCCESS; + + case CMD_DIRECT_CLEAR: + TRY(UU_SIPO_DirectClear(unit)); + return E_SUCCESS; + + case CMD_DIRECT_SHIFT: + TRY(UU_SIPO_DirectShift(unit)); + return E_SUCCESS; + + case CMD_DIRECT_STORE: + TRY(UU_SIPO_DirectStore(unit)); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_SIPO = { + .name = "SIPO", + .description = "Shift register driver (595, 4094)", + // Settings + .preInit = USIPO_preInit, + .cfgLoadBinary = USIPO_loadBinary, + .cfgWriteBinary = USIPO_writeBinary, + .cfgLoadIni = USIPO_loadIni, + .cfgWriteIni = USIPO_writeIni, + // Init + .init = USIPO_init, + .deInit = USIPO_deInit, + // Function + .handleRequest = USIPO_handleRequest, +}; diff --git a/GexUnits/sipo/unit_sipo.h b/GexUnits/sipo/unit_sipo.h new file mode 100644 index 0000000..498a040 --- /dev/null +++ b/GexUnits/sipo/unit_sipo.h @@ -0,0 +1,16 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital input unit; single or multiple pin read access on one port (A-F) +// + +#ifndef U_SIPO_H +#define U_SIPO_H + +#include "unit.h" + +extern const UnitDriver UNIT_SIPO; + +// UU_ prototypes + +#endif //U_SIPO_H diff --git a/GexUnits/spi/_spi_api.c b/GexUnits/spi/_spi_api.c new file mode 100644 index 0000000..06386a4 --- /dev/null +++ b/GexUnits/spi/_spi_api.c @@ -0,0 +1,103 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_spi.h" + +#define SPI_INTERNAL +#include "_spi_internal.h" + +static error_t spi_wait_until_flag(struct priv *priv, uint32_t flag, bool stop_state) +{ + uint32_t t_start = HAL_GetTick(); + while (((priv->periph->SR & flag) != 0) != stop_state) { + if (HAL_GetTick() - t_start > 10) { + return E_HW_TIMEOUT; + } + } + return E_SUCCESS; +} + +/** + * Perform a low level SPI transfer + * + * @param priv - private object of the SPI unit + * @param request - request buffer + * @param response - response buffer + * @param req_len - request len + * @param resp_skip - response skip bytes + * @param resp_len - response len + * @return success + */ +static error_t xfer_do(struct priv *priv, const uint8_t *request, + uint8_t *response, + uint32_t req_len, + uint32_t resp_skip, + uint32_t resp_len) +{ + // TODO this is slow, use DMA + + if (response == NULL) resp_len = 0; + + // avoid skip causing stretch beyond tx window if nothing is to be read back + if (resp_len == 0) resp_skip = 0; + + // in tx only mode, return zeros + if (priv->tx_only && resp_len>0) { + memset(response, 0, resp_len); + } + + uint8_t tb; + uint32_t end = MAX(req_len, resp_len + resp_skip); + for (uint32_t i = 0; i < end; i++) { + if (i < req_len) tb = *request++; + else tb = 0; + + TRY(spi_wait_until_flag(priv, SPI_SR_TXE, true)); + LL_SPI_TransmitData8(priv->periph, tb); + + if (!priv->tx_only) { + TRY(spi_wait_until_flag(priv, SPI_SR_RXNE, true)); + uint8_t rb = LL_SPI_ReceiveData8(priv->periph); + + if (resp_skip > 0) resp_skip--; + else if (resp_len > 0) { + resp_len--; + *response++ = rb; + } + } + } + return E_SUCCESS; +} + +error_t UU_SPI_Multicast(Unit *unit, uint16_t slaves, + const uint8_t *request, uint32_t req_len) +{ + struct priv *priv= unit->data; + uint16_t mask = pinmask_spread(slaves, priv->ssn_pins); + priv->ssn_port->BRR = mask; + { + TRY(xfer_do(priv, request, NULL, req_len, 0, 0)); + } + priv->ssn_port->BSRR = mask; + + return E_SUCCESS; +} + +error_t UU_SPI_Write(Unit *unit, uint8_t slave_num, + const uint8_t *request, uint8_t *response, + uint32_t req_len, uint32_t resp_skip, uint32_t resp_len) +{ + struct priv *priv= unit->data; + + uint16_t mask = pinmask_spread((uint16_t) (1 << slave_num), priv->ssn_pins); + priv->ssn_port->BRR = mask; + { + TRY(xfer_do(priv, request, response, req_len, resp_skip, resp_len)); + } + priv->ssn_port->BSRR = mask; + + return E_SUCCESS; +} diff --git a/GexUnits/spi/_spi_init.c b/GexUnits/spi/_spi_init.c new file mode 100644 index 0000000..6adfc55 --- /dev/null +++ b/GexUnits/spi/_spi_init.c @@ -0,0 +1,195 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define SPI_INTERNAL +#include "_spi_internal.h" + +/** Allocate data structure and set defaults */ +error_t USPI_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // some defaults + priv->periph_num = 1; + priv->prescaller = 64; + priv->remap = 0; + + priv->cpol = 0; + priv->cpha = 0; + priv->tx_only = false; + priv->lsb_first = false; + + priv->ssn_port_name = 'A'; + priv->ssn_pins = 0x0001; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t USPI_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (!(priv->periph_num >= 1 && priv->periph_num <= 2)) { + dbg("!! Bad SPI periph"); + // XXX some chips have also SPI3 + return E_BAD_CONFIG; + } + + // assign and claim the peripheral + if (priv->periph_num == 1) { + TRY(rsc_claim(unit, R_SPI1)); + priv->periph = SPI1; + } + else if (priv->periph_num == 2) { + TRY(rsc_claim(unit, R_SPI2)); + priv->periph = SPI2; + } + + // This is written for F072, other platforms will need adjustments + + // Configure SPI own pins (AF) + + char spi_portname; + uint8_t pin_miso; + uint8_t pin_mosi; + uint8_t pin_sck; + uint32_t af_spi; + + // TODO +#if STM32F072xB + // SPI1 - many options + // sck, miso, mosi, af + + if (priv->periph_num == 1) { + // SPI1 + if (priv->remap == 0) { + spi_portname = 'A'; + af_spi = LL_GPIO_AF_0; + pin_sck = 5; + pin_miso = 6; + pin_mosi = 7; + } + else if (priv->remap == 1) { + spi_portname = 'B'; + af_spi = LL_GPIO_AF_0; + pin_sck = 3; + pin_miso = 4; + pin_mosi = 5; + } +// else if (priv->remap == 2) { +// // large packages only +// spi_portname = 'E'; +// af_spi = LL_GPIO_AF_1; +// pin_sck = 13; +// pin_miso = 14; +// pin_mosi = 15; +// } + else { + return E_BAD_CONFIG; + } + } + else { + // SPI2 + if (priv->remap == 0) { + spi_portname = 'B'; + af_spi = LL_GPIO_AF_0; + pin_sck = 13; + pin_miso = 14; + pin_mosi = 15; + } +// else if (priv->remap == 1) { +// // NOTE: there's also an incomplete remap in PB and PC +// spi_portname = 'D'; +// af_spi = LL_GPIO_AF_0; +// pin_sck = 1; +// pin_miso = 3; +// pin_mosi = 4; +// } + else { + return E_BAD_CONFIG; + } + } + +#elif GEX_PLAT_F103_BLUEPILL + #error "NO IMPL" +#elif GEX_PLAT_F303_DISCOVERY + #error "NO IMPL" +#elif GEX_PLAT_F407_DISCOVERY + #error "NO IMPL" +#else + #error "BAD PLATFORM!" +#endif + + // first, we have to claim the pins + TRY(rsc_claim_pin(unit, spi_portname, pin_mosi)); + TRY(rsc_claim_pin(unit, spi_portname, pin_miso)); + TRY(rsc_claim_pin(unit, spi_portname, pin_sck)); + + TRY(hw_configure_gpio_af(spi_portname, pin_mosi, af_spi)); + TRY(hw_configure_gpio_af(spi_portname, pin_miso, af_spi)); + TRY(hw_configure_gpio_af(spi_portname, pin_sck, af_spi)); + + // configure SSN GPIOs + { + // Claim all needed pins + TRY(rsc_claim_gpios(unit, priv->ssn_port_name, priv->ssn_pins)); + TRY(hw_configure_sparse_pins(priv->ssn_port_name, priv->ssn_pins, &priv->ssn_port, + LL_GPIO_MODE_OUTPUT, LL_GPIO_OUTPUT_PUSHPULL)); + // Set the initial state - all high + priv->ssn_port->BSRR = priv->ssn_pins; + } + + hw_periph_clock_enable(priv->periph); + + // Configure SPI - must be configured under reset + LL_SPI_Disable(priv->periph); + { + uint32_t presc = priv->prescaller; + uint32_t lz = __CLZ(presc); + if (lz < 23) lz = 23; + if (lz > 30) lz = 30; + presc = (32 - lz - 2); + LL_SPI_SetBaudRatePrescaler(priv->periph, (presc<periph, priv->cpol ? LL_SPI_POLARITY_HIGH : LL_SPI_POLARITY_LOW); + LL_SPI_SetClockPhase(priv->periph, priv->cpha ? LL_SPI_PHASE_1EDGE : LL_SPI_PHASE_2EDGE); + LL_SPI_SetTransferDirection(priv->periph, priv->tx_only ? LL_SPI_HALF_DUPLEX_TX : LL_SPI_FULL_DUPLEX); + LL_SPI_SetTransferBitOrder(priv->periph, priv->lsb_first ? LL_SPI_LSB_FIRST : LL_SPI_MSB_FIRST); + + LL_SPI_SetNSSMode(priv->periph, LL_SPI_NSS_SOFT); + LL_SPI_SetDataWidth(priv->periph, LL_SPI_DATAWIDTH_8BIT); + LL_SPI_SetRxFIFOThreshold(priv->periph, LL_SPI_RX_FIFO_TH_QUARTER); // trigger RXNE on 1 byte + + LL_SPI_SetMode(priv->periph, LL_SPI_MODE_MASTER); + } + LL_SPI_Enable(priv->periph); + + return E_SUCCESS; +} + +/** Tear down the unit */ +void USPI_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init the pins & peripheral only if inited correctly + if (unit->status == E_SUCCESS) { + assert_param(priv->periph); + LL_SPI_DeInit(priv->periph); + + hw_periph_clock_disable(priv->periph); + } + + // Release all resources + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/spi/_spi_internal.h b/GexUnits/spi/_spi_internal.h new file mode 100644 index 0000000..b1518dc --- /dev/null +++ b/GexUnits/spi/_spi_internal.h @@ -0,0 +1,59 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_SPI_INTERNAL_H +#define GEX_F072_SPI_INTERNAL_H + +#ifndef SPI_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + uint8_t periph_num; //!< 1 or 2 + uint8_t remap; //!< SPI remap option + + uint16_t prescaller; //!< Clock prescaller, stored as the dividing factor + bool cpol; //!< CPOL setting + bool cpha; //!< CPHA setting + bool tx_only; //!< If true, Enable only the MOSI line + + bool lsb_first; //!< Option to send LSB first + char ssn_port_name; //!< SSN port + uint16_t ssn_pins; //!< SSN pin mask + + SPI_TypeDef *periph; + GPIO_TypeDef *ssn_port; +}; + +// ------------------------------------------------------------------------ + +/** Load from a binary buffer stored in Flash */ +void USPI_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void USPI_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t USPI_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void USPI_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Allocate data structure and set defaults */ +error_t USPI_preInit(Unit *unit); + +/** Finalize unit set-up */ +error_t USPI_init(Unit *unit); + +/** Tear down the unit */ +void USPI_deInit(Unit *unit); + +#endif //GEX_F072_SPI_INTERNAL_H diff --git a/GexUnits/spi/_spi_settings.c b/GexUnits/spi/_spi_settings.c new file mode 100644 index 0000000..4cbc510 --- /dev/null +++ b/GexUnits/spi/_spi_settings.c @@ -0,0 +1,141 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define SPI_INTERNAL +#include "_spi_internal.h" + +/** Load from a binary buffer stored in Flash */ +void USPI_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->periph_num = pp_u8(pp); + priv->prescaller = pp_u16(pp); + priv->remap = pp_u8(pp); + + priv->cpol = pp_bool(pp); + priv->cpha = pp_bool(pp); + priv->tx_only = pp_bool(pp); + priv->lsb_first = pp_bool(pp); + + priv->ssn_port_name = pp_char(pp); + priv->ssn_pins = pp_u16(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void USPI_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_u8(pb, priv->periph_num); + pb_u16(pb, priv->prescaller); + pb_u8(pb, priv->remap); + + pb_bool(pb, priv->cpol); + pb_bool(pb, priv->cpha); + pb_bool(pb, priv->tx_only); + pb_bool(pb, priv->lsb_first); + + pb_char(pb, priv->ssn_port_name); + pb_u16(pb, priv->ssn_pins); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t USPI_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "device")) { + priv->periph_num = cfg_u8_parse(value, &suc); + } + else if (streq(key, "remap")) { + priv->remap = cfg_u8_parse(value, &suc); + } + else if (streq(key, "prescaller")) { + priv->prescaller = cfg_u16_parse(value, &suc); + } + else if (streq(key, "cpol")) { + priv->cpol = cfg_bool_parse(value, &suc); + } + else if (streq(key, "cpha")) { + priv->cpha = cfg_bool_parse(value, &suc); + } + else if (streq(key, "tx-only")) { + priv->tx_only = cfg_bool_parse(value, &suc); + } + else if (streq(key, "first-bit")) { + priv->lsb_first = (bool) cfg_enum2_parse(value, "MSB", 0, "LSB", 1, &suc); + } + else if (streq(key, "port")) { + suc = cfg_port_parse(value, &priv->ssn_port_name); + } + else if (streq(key, "pins")) { + priv->ssn_pins = cfg_pinmask_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void USPI_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Peripheral number (SPIx)"); + iw_entry_d(iw, "device", priv->periph_num); + + // TODO show a legend for peripherals and remaps + iw_comment(iw, "Pin mappings (SCK,MISO,MOSI)"); +#if STM32F072xB + iw_comment(iw, " SPI1: (0) A5,A6,A7 (1) B3,B4,B5"); // (2) E13,E14,E15 + iw_comment(iw, " SPI2: (0) B13,B14,B15"); // (1) D1,D3,D4 +#elif GEX_PLAT_F103_BLUEPILL + #error "NO IMPL" +#elif GEX_PLAT_F303_DISCOVERY + #error "NO IMPL" +#elif GEX_PLAT_F407_DISCOVERY + #error "NO IMPL" +#else + #error "BAD PLATFORM!" +#endif + iw_entry_d(iw, "remap", priv->remap); + + iw_cmt_newline(iw); + iw_comment(iw, "Prescaller: 2,4,8,...,256"); + iw_entry_d(iw, "prescaller", priv->prescaller); + + iw_comment(iw, "Clock polarity: 0,1 (clock idle level)"); + iw_entry_d(iw, "cpol", priv->cpol); + + iw_comment(iw, "Clock phase: 0,1 (active edge, 0-first, 1-second)"); + iw_entry_d(iw, "cpha", priv->cpha); + + iw_comment(iw, "Transmit only, disable MISO"); + iw_entry_s(iw, "tx-only", str_yn(priv->tx_only)); + + iw_comment(iw, "Bit order (LSB or MSB first)"); + iw_entry_s(iw, "first-bit", cfg_enum2_encode((uint32_t) priv->lsb_first, 0, "MSB", 1, "LSB")); + + iw_cmt_newline(iw); + iw_comment(iw, "SS port name"); + iw_entry(iw, "port", "%c", priv->ssn_port_name); + + iw_comment(iw, "SS pins (comma separated, supports ranges)"); + iw_entry_s(iw, "pins", cfg_pinmask_encode(priv->ssn_pins, unit_tmp512, 0)); +} diff --git a/GexUnits/spi/unit_spi.c b/GexUnits/spi/unit_spi.c new file mode 100644 index 0000000..a432058 --- /dev/null +++ b/GexUnits/spi/unit_spi.c @@ -0,0 +1,84 @@ +// +// Created by MightyPork on 2018/01/02. +// +// SPI master with unicast and multicats support, up to 16 slave select lines +// + +#include "comm/messages.h" +#include "unit_base.h" +#include "utils/avrlibc.h" +#include "unit_spi.h" + +#define SPI_INTERNAL +#include "_spi_internal.h" + +// SPI master + +enum PinCmd_ { + CMD_QUERY = 0, + CMD_MULTICAST = 1, +}; + +/** Handle a request message */ +static error_t USPI_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + uint8_t slave; + uint16_t slaves; + uint16_t req_len; + uint16_t resp_skip; + uint16_t resp_len; + const uint8_t *bb; + + uint32_t len; + + switch (command) { + /** Write and read byte(s) - slave_num:u8, req_len:u16, resp_skip:u16, resp_len:u16, byte(s) */ + case CMD_QUERY: + slave = pp_u8(pp); + resp_skip = pp_u16(pp); + resp_len = pp_u16(pp); + + bb = pp_tail(pp, &len); + + TRY(UU_SPI_Write(unit, slave, + bb, (uint8_t *) unit_tmp512, + len, resp_skip, resp_len)); + + // no response if we aren't reading + if (resp_len > 0) { + com_respond_buf(frame_id, MSG_SUCCESS, (uint8_t *) unit_tmp512, resp_len); + } + return E_SUCCESS; + + /** Write byte(s) to multiple slaves - slaves:u16, req_len:u16, byte(s) */ + case CMD_MULTICAST: + slaves = pp_u16(pp); + + bb = pp_tail(pp, &len); + + TRY(UU_SPI_Multicast(unit, slaves, bb, len)); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_SPI = { + .name = "SPI", + .description = "SPI master", + // Settings + .preInit = USPI_preInit, + .cfgLoadBinary = USPI_loadBinary, + .cfgWriteBinary = USPI_writeBinary, + .cfgLoadIni = USPI_loadIni, + .cfgWriteIni = USPI_writeIni, + // Init + .init = USPI_init, + .deInit = USPI_deInit, + // Function + .handleRequest = USPI_handleRequest, +}; diff --git a/GexUnits/spi/unit_spi.h b/GexUnits/spi/unit_spi.h new file mode 100644 index 0000000..bfa3742 --- /dev/null +++ b/GexUnits/spi/unit_spi.h @@ -0,0 +1,63 @@ +// +// Created by MightyPork on 2018/01/02. +// + +#ifndef GEX_F072_UNIT_SPI_H +#define GEX_F072_UNIT_SPI_H + +#include "unit.h" + +extern const UnitDriver UNIT_SPI; + +// Unit-to-Unit API + +/** + * Raw read/write via SPI. + * It's possible to simultaneously write and read, or skip bytes in either direction. + * + * Example scenarios: + * + * req 2, skip 2, read 3 + * |<-- req_len --->| + * [ write ][ write ] . . . . . . . . + * . . . . . . . . . [ read ][ read ][ read ] + * |<-- resp_skip ->|<------ resp_len ----->| + * + * req 2, skip 0, read 2 + * |<-- req_len --->| + * [ write ][ write ] + * [ read ][ read ] + * |<-- resp_len -->| + * + * @param unit - SPI unit + * @param slave_num - slave number (SS pin index, counted from least significant bit) + * @param request - request bytes buffer + * @param response - response bytes buffer + * @param req_len - number of bytes in the request. Will be right-padded with zeros. + * @param resp_skip - response bytes to discard before starting to capture them + * @param resp_len - number of bytes to capture, after discarding resp_skip received bytes + * @return success + */ +error_t UU_SPI_Write(Unit *unit, uint8_t slave_num, + const uint8_t *request, + uint8_t *response, + uint32_t req_len, + uint32_t resp_skip, + uint32_t resp_len); + +/** + * Write to multiple slaves at once. + * This is similar to UU_SPI_Write, but performs no read and works only if the device + * is configured as tx-only. + * + * @param unit - SPI unit + * @param slaves - bitmap of slaves to write (packed bits representing the SSN pins) + * @param request - request bytes buffer + * @param req_len - length of the request buffer + * @return success + */ +error_t UU_SPI_Multicast(Unit *unit, uint16_t slaves, + const uint8_t *request, + uint32_t req_len); + +#endif //GEX_F072_UNIT_SPI_H diff --git a/GexUnits/template/!README.TXT b/GexUnits/template/!README.TXT new file mode 100644 index 0000000..d7cf9a3 --- /dev/null +++ b/GexUnits/template/!README.TXT @@ -0,0 +1,2 @@ +This is a template unit, used for reference when creating new units. +It is not registered into the unit registry, and cannot be instantiated. diff --git a/GexUnits/template/_tpl_api.c b/GexUnits/template/_tpl_api.c new file mode 100644 index 0000000..0779fc2 --- /dev/null +++ b/GexUnits/template/_tpl_api.c @@ -0,0 +1,11 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_tpl.h" + +#define TPL_INTERNAL +#include "_tpl_internal.h" + diff --git a/GexUnits/template/_tpl_init.c b/GexUnits/template/_tpl_init.c new file mode 100644 index 0000000..ae9e1e8 --- /dev/null +++ b/GexUnits/template/_tpl_init.c @@ -0,0 +1,49 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define TPL_INTERNAL +#include "_tpl_internal.h" + +/** Allocate data structure and set defaults */ +error_t UTPL_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UTPL_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // + + return E_SUCCESS; +} + + +/** Tear down the unit */ +void UTPL_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS ) { + // + } + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/template/_tpl_internal.h b/GexUnits/template/_tpl_internal.h new file mode 100644 index 0000000..b94d7b1 --- /dev/null +++ b/GexUnits/template/_tpl_internal.h @@ -0,0 +1,46 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_TPL_INTERNAL_H +#define GEX_F072_TPL_INTERNAL_H + +#ifndef TPL_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +/** Private data structure */ +struct priv { + // settings + + // internal state +}; + +/** Allocate data structure and set defaults */ +error_t UTPL_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UTPL_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UTPL_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UTPL_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UTPL_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UTPL_init(Unit *unit); + +/** Tear down the unit */ +void UTPL_deInit(Unit *unit); + +#endif //GEX_F072_TPL_INTERNAL_H diff --git a/GexUnits/template/_tpl_settings.c b/GexUnits/template/_tpl_settings.c new file mode 100644 index 0000000..5d71a7e --- /dev/null +++ b/GexUnits/template/_tpl_settings.c @@ -0,0 +1,58 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define TPL_INTERNAL +#include "_tpl_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UTPL_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + // +} + +/** Write to a binary buffer for storing in Flash */ +void UTPL_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + // +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UTPL_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (false) { + // + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UTPL_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + // +} + diff --git a/GexUnits/template/install.sh b/GexUnits/template/install.sh new file mode 100755 index 0000000..7feac33 --- /dev/null +++ b/GexUnits/template/install.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +echo "Enter unit type identifier (empty to cancel):" +read x + +if [ -e $x ]; then + exit; +fi + +xl="${x,,}" +xu="${x^^}" + +for f in *.h; do mv -- "$f" "${f//tpl/$xl}"; done +for f in *.c; do mv -- "$f" "${f//tpl/$xl}"; done + +sed "s/tpl/$xl/" -i *.h +sed "s/TPL/$xu/" -i *.h +sed "s/tpl/$xl/" -i *.c +sed "s/TPL/$xu/" -i *.c + +echo "Unit $xu set up completed. Removing installer.." +rm '!README.TXT' +rm $0 diff --git a/GexUnits/template/unit_tpl.c b/GexUnits/template/unit_tpl.c new file mode 100644 index 0000000..30ce290 --- /dev/null +++ b/GexUnits/template/unit_tpl.c @@ -0,0 +1,57 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_tpl.h" + +#define TPL_INTERNAL +#include "_tpl_internal.h" + +// ------------------------------------------------------------------------ + +enum TplCmd_ { + // +}; + +/** Handle a request message */ +static error_t UTPL_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, + PayloadParser *pp) +{ + switch (command) { + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** + * Handle update-tick (if configured in init) + * + * @param unit + */ +static void UTPL_updateTick(Unit *unit) +{ + // +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_TPL = { + .name = "TPL", + .description = "Template unit", + // Settings + .preInit = UTPL_preInit, + .cfgLoadBinary = UTPL_loadBinary, + .cfgWriteBinary = UTPL_writeBinary, + .cfgLoadIni = UTPL_loadIni, + .cfgWriteIni = UTPL_writeIni, + // Init + .init = UTPL_init, + .deInit = UTPL_deInit, + // Function + .handleRequest = UTPL_handleRequest, + .updateTick = UTPL_updateTick, +}; diff --git a/GexUnits/template/unit_tpl.h b/GexUnits/template/unit_tpl.h new file mode 100644 index 0000000..a0865a5 --- /dev/null +++ b/GexUnits/template/unit_tpl.h @@ -0,0 +1,16 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Digital input unit; single or multiple pin read access on one port (A-F) +// + +#ifndef U_TPL_H +#define U_TPL_H + +#include "unit.h" + +extern const UnitDriver UNIT_TPL; + +// UU_ prototypes + +#endif //U_TPL_H diff --git a/GexUnits/test/unit_test.c b/GexUnits/test/unit_test.c new file mode 100644 index 0000000..c551f51 --- /dev/null +++ b/GexUnits/test/unit_test.c @@ -0,0 +1,177 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "comm/messages.h" +#include "unit_base.h" +#include "unit_test.h" + +/** Private data structure */ +struct priv { + uint32_t unused; +}; + +// ------------------------------------------------------------------------ + +/** Load from a binary buffer stored in Flash */ +static void Tst_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + // +} + +/** Write to a binary buffer for storing in Flash */ +static void Tst_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + // +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +static error_t Tst_loadIni(Unit *unit, const char *key, const char *value) +{ + return E_BAD_KEY; +} + +/** Generate INI file section for the unit */ +static void Tst_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + // +} + +// ------------------------------------------------------------------------ + +/** Allocate data structure and set defaults */ +static error_t Tst_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +static error_t Tst_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + // + + return E_SUCCESS; +} + +/** Tear down the unit */ +static void Tst_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // + + // Free memory + free_ck(unit->data); +} + +// ------------------------------------------------------------------------ + +enum PinCmd_ { + CMD_PING = 0, + CMD_ECHO = 1, + CMD_BULKREAD = 2, + CMD_BULKWRITE = 3, +}; + +// this is a very long text for testing bulk read +static const char *longtext = "The history of all hitherto existing societies is the history of class struggles.\n\nFreeman and slave, patrician and plebeian, lord and serf, guild-master and journeyman, in a word, oppressor and oppressed, stood in constant opposition to one another, carried on an uninterrupted, now hidden, now open fight, a fight that each time ended, either in a revolutionary re-constitution of society at large, or in the common ruin of the contending classes.\n\nIn the earlier epochs of history, we find almost everywhere a complicated arrangement of society into various orders, a manifold gradation of social rank. In ancient Rome we have patricians, knights, plebeians, slaves; in the Middle Ages, feudal lords, vassals, guild-masters, journeymen, apprentices, serfs; in almost all of these classes, again, subordinate gradations.\n\nThe modern bourgeois society that has sprouted from the ruins of feudal society has not done away with class antagonisms. It has but established new classes, new conditions of oppression, new forms of struggle in place of the old ones. Our epoch, the epoch of the bourgeoisie, possesses, however, this distinctive feature: it has simplified the class antagonisms. Society as a whole is more and more splitting up into two great hostile camps, into two great classes, directly facing each other: Bourgeoisie and Proletariat.\n\nFrom the serfs of the Middle Ages sprang the chartered burghers of the earliest towns. From these burgesses the first elements of the bourgeoisie were developed.\n\nThe discovery of America, the rounding of the Cape, opened up fresh ground for the rising bourgeoisie. The East-Indian and Chinese markets, the colonisation of America, trade with the colonies, the increase in the means of exchange and in commodities generally, gave to commerce, to navigation, to industry, an impulse never before known, and thereby, to the revolutionary element in the tottering feudal society, a rapid development.\n\nThe feudal system of industry, under which industrial production was monopolised by closed guilds, now no longer sufficed for the growing wants of the new markets. The manufacturing system took its place. The guild-masters were pushed on one side by the manufacturing middle class; division of labour between the different corporate guilds vanished in the face of division of labour in each single workshop.\n\nMeantime the markets kept ever growing, the demand ever rising. Even manufacture no longer sufficed. Thereupon, steam and machinery revolutionised industrial production. The place of manufacture was taken by the giant, Modern Industry, the place of the industrial middle class, by industrial millionaires, the leaders of whole industrial armies, the modern bourgeois.\n\nModern industry has established the world-market, for which the discovery of America paved the way. This market has given an immense development to commerce, to navigation, to communication by land. This development has, in its time, reacted on the extension of industry; and in proportion as industry, commerce, navigation, railways extended, in the same proportion the bourgeoisie developed, increased its capital, and pushed into the background every class handed down from the Middle Ages.\n\nWe see, therefore, how the modern bourgeoisie is itself the product of a long course of development, of a series of revolutions in the modes of production and of exchange.\n\nEach step in the development of the bourgeoisie was accompanied by a corresponding political advance of that class. An oppressed class under the sway of the feudal nobility, an armed and self-governing association in the mediaeval commune; here independent urban republic (as in Italy and Germany), there taxable \"third estate\" of the monarchy (as in France), afterwards, in the period of manufacture proper, serving either the semi-feudal or the absolute monarchy as a counterpoise against the nobility, and, in fact, corner-stone of the great monarchies in general, the bourgeoisie has at last, since the establishment of Modern Industry and of the world-market, conquered for itself, in the modern representative State, exclusive political sway. The executive of the modern State is but a committee for managing the common affairs of the whole bourgeoisie."; + +static void br_longtext(struct bulk_read *bulk, uint32_t chunk, uint8_t *buffer) +{ + // clean-up request + if (buffer == NULL) { + free_ck(bulk); + return; + } + + memcpy(buffer, longtext+bulk->offset, chunk); +} + +static void bw_dump(struct bulk_write *bulk, const uint8_t *chunk, uint32_t len) +{ + // clean-up request + if (chunk == NULL) { + free_ck(bulk); + return; + } + + dbg("\r\nBulk write at %d, len %d", (int)bulk->offset, (int)len); + PUTSN((const char *) chunk, (uint16_t) len); + PUTS("\r\n"); +} + +/** Handle a request message */ +static error_t Tst_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + switch (command) { + case CMD_PING: + com_respond_ok(frame_id); + return E_SUCCESS; + + case CMD_ECHO:; + uint32_t len; + const uint8_t *data = pp_tail(pp, &len); + com_respond_buf(frame_id, MSG_SUCCESS, data, len); + return E_SUCCESS; + + case CMD_BULKREAD:; + BulkRead *br = malloc_ck(sizeof(struct bulk_read)); + assert_param(br); + + br->len = (uint32_t) strlen(longtext); + br->frame_id = frame_id; + br->read = br_longtext; + + bulkread_start(comm, br); + return E_SUCCESS; + + case CMD_BULKWRITE:; + BulkWrite *bw = malloc_ck(sizeof(struct bulk_write)); + assert_param(bw); + + bw->len = 10240; + bw->frame_id = frame_id; + bw->write = bw_dump; + + bulkwrite_start(comm, bw); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_TEST = { + .name = "TEST", + .description = "Test unit", + // Settings + .preInit = Tst_preInit, + .cfgLoadBinary = Tst_loadBinary, + .cfgWriteBinary = Tst_writeBinary, + .cfgLoadIni = Tst_loadIni, + .cfgWriteIni = Tst_writeIni, + // Init + .init = Tst_init, + .deInit = Tst_deInit, + // Function + .handleRequest = Tst_handleRequest, +}; diff --git a/GexUnits/test/unit_test.h b/GexUnits/test/unit_test.h new file mode 100644 index 0000000..1f1f84e --- /dev/null +++ b/GexUnits/test/unit_test.h @@ -0,0 +1,14 @@ +// +// Created by MightyPork on 2017/11/25. +// +// Testing unit that uses most of the protocol to verify the core functionality +// + +#ifndef U_TEST_H +#define U_TEST_H + +#include "unit.h" + +extern const UnitDriver UNIT_TEST; + +#endif //U_TEST_H diff --git a/GexUnits/touch/_touch_api.c b/GexUnits/touch/_touch_api.c new file mode 100644 index 0000000..5eec0e4 --- /dev/null +++ b/GexUnits/touch/_touch_api.c @@ -0,0 +1,11 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_touch.h" + +#define TOUCH_INTERNAL +#include "_touch_internal.h" + diff --git a/GexUnits/touch/_touch_core.c b/GexUnits/touch/_touch_core.c new file mode 100644 index 0000000..dc46e15 --- /dev/null +++ b/GexUnits/touch/_touch_core.c @@ -0,0 +1,217 @@ +// +// Created by MightyPork on 2018/02/25. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_touch.h" + +#define TOUCH_INTERNAL + +#include "_touch_internal.h" + +// discharge time in ms +#define DIS_TIME 1 + +static void startNextPhase(Unit *unit); + +static void UTOUCH_EventReportJob(Job *job) +{ + Unit *unit = job->unit; + struct priv *priv = unit->data; + + uint8_t buf[8]; + PayloadBuilder pb = pb_start(buf, 8, NULL); + pb_u32(&pb, pinmask_pack_32(~job->data1, priv->all_channels_mask)); // inverted and packed - all pins (pressed state) + pb_u32(&pb, pinmask_pack_32(job->data2, priv->all_channels_mask)); // trigger generating pins + assert_param(pb.ok); + + EventReport er = { + .unit = unit, + .type = 0x00, + .length = 8, + .data = buf, + .timestamp = job->timestamp, + }; + + EventReport_Send(&er); +} + +static void UTOUCH_CheckForBinaryEvents(Unit *const unit) +{ + struct priv *priv = unit->data; + + const uint32_t time_ms = PTIM_GetTime(); + + if (priv->last_done_ms == 0) { + // avoid bug with trigger on first capture + priv->last_done_ms = time_ms; + } + + const uint64_t ts = PTIM_GetMicrotime(); + uint32_t eventpins = 0; + + const uint16_t ms_elapsed = (uint16_t) (time_ms - priv->last_done_ms); + + for (uint16_t i = 0; i < 32; i++) { + const uint32_t poke = (uint32_t) (1 << i); + if (0 == (priv->all_channels_mask & poke)) continue; + if (priv->binary_thr[i] == 0) continue; // skip disabled channels + + const bool isactive = (bool) (priv->binary_active_bits & poke); + const bool can_go_up = !isactive && (priv->readouts[i] > (priv->binary_thr[i] + priv->binary_hysteresis)); + const bool can_go_down = isactive && (priv->readouts[i] < priv->binary_thr[i]); + + if (can_go_up) { + priv->bin_trig_cnt[i] += ms_elapsed; + if (priv->bin_trig_cnt[i] >= priv->binary_debounce_ms) { + priv->binary_active_bits |= poke; + priv->bin_trig_cnt[i] = 0; // reset for the other direction of the switch + + eventpins |= poke; + } + } + else if (priv->bin_trig_cnt[i] > 0) { + priv->bin_trig_cnt[i] = 0; + } + + if (can_go_down) { + priv->bin_trig_cnt[i] -= ms_elapsed; + if (priv->bin_trig_cnt[i] <= -priv->binary_debounce_ms) { + priv->binary_active_bits &= ~poke; + priv->bin_trig_cnt[i] = 0; // reset for the other direction of the switch + + eventpins |= poke; + } + } + else if (priv->bin_trig_cnt[i] < 0) { + priv->bin_trig_cnt[i] = 0; + } + } + + if (eventpins != 0) { + Job j = { + .timestamp = ts, + .data1 = priv->binary_active_bits, + .data2 = eventpins, + .unit = unit, + .cb = UTOUCH_EventReportJob, + }; + + scheduleJob(&j); + } + + priv->last_done_ms = time_ms; +} + +void UTOUCH_HandleIrq(void *arg) +{ + Unit *unit = arg; + struct priv *priv = unit->data; + + if (TSC->ISR & TSC_ISR_MCEF) { + priv->status = UTSC_STATUS_FAIL; + dbg_touch("TSC Failure."); + TSC->ICR = TSC_ICR_EOAIC | TSC_ICR_MCEIC; + } + + if (TSC->ISR & TSC_ISR_EOAF) { + TSC->ICR = TSC_ICR_EOAIC; +// assert_param((TSC->IOGCSR>>16) == priv->groups_phase[priv->next_phase]); + + // Store captured data + const uint32_t chmask = TSC->IOCCR; + for (int i = 0; i < 32; i++) { + if (chmask & (1<readouts[i] = (uint16_t) (TSC->IOGXCR[i >> 2] & 0x3FFF); + } + } + + priv->next_phase++; + + if (!priv->cfg.interlaced) { + // check if we've run out of existing or populated groups + if (priv->next_phase == 3 || priv->groups_phase[priv->next_phase] == 0) { + priv->next_phase = 0; + priv->status = UTSC_STATUS_READY; + UTOUCH_CheckForBinaryEvents(unit); + } + } + + TSC->CR &= ~TSC_CR_IODEF; // pull low - discharge + } + + priv->ongoing = false; + priv->discharge_delay = DIS_TIME; +} + +#if TSC_DEBUG +static volatile uint32_t xcnt=0; +#endif + +void UTOUCH_updateTick(Unit *unit) +{ +#if TSC_DEBUG + xcnt++; +#endif + + struct priv *priv = unit->data; + + if (priv->ongoing) { + return; + } + + if (priv->discharge_delay > 0) { + priv->discharge_delay--; + } else { + startNextPhase(unit); + } + +#if TSC_DEBUG + if(xcnt >= 250) { + xcnt=0; + PRINTF("> "); + for (int i = 0; i < 32; i++) { + if (priv->all_channels_mask & (1<readouts[i]); + } + } + PRINTF("\r\n"); + } +#endif +} + +static void startNextPhase(Unit *unit) +{ + struct priv *priv = unit->data; + + if (priv->all_channels_mask == 0) return; + + if (priv->cfg.interlaced) { + // Find the next non-zero bit, wrap around if needed + while ((priv->all_channels_mask & (1<next_phase))==0) { + priv->next_phase++; + if (priv->next_phase == 32) { + priv->next_phase = 0; + priv->status = UTSC_STATUS_READY; + UTOUCH_CheckForBinaryEvents(unit); + } + } + TSC->IOGCSR = (uint32_t) (1 << (priv->next_phase >> 2)); // phase divided by 4 + TSC->IOCCR = (uint32_t) (1 << priv->next_phase); + + // interlaced - float neighbouring electrodes + TSC->CR |= TSC_CR_IODEF; + } else { + TSC->IOGCSR = priv->groups_phase[priv->next_phase]; + TSC->IOCCR = priv->channels_phase[priv->next_phase]; + + // separate - keep neighbouring electrodes at GND + } + + TSC->ICR = TSC_ICR_EOAIC | TSC_ICR_MCEIC; + + // Go! + priv->ongoing = true; + TSC->CR |= TSC_CR_START; +} diff --git a/GexUnits/touch/_touch_init.c b/GexUnits/touch/_touch_init.c new file mode 100644 index 0000000..bb897f3 --- /dev/null +++ b/GexUnits/touch/_touch_init.c @@ -0,0 +1,221 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define TOUCH_INTERNAL + +#include "_touch_internal.h" + +/** Allocate data structure and set defaults */ +error_t UTOUCH_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + priv->cfg.charge_time = 2; + priv->cfg.drain_time = 2; + priv->cfg.spread_deviation = 0; + priv->cfg.ss_presc = 1; + priv->cfg.pg_presc = 32; + priv->cfg.sense_timeout = 7; + memset(priv->cfg.group_scaps, 0, 8); + memset(priv->cfg.group_channels, 0, 8); + priv->cfg.binary_hysteresis = 10; + priv->cfg.binary_debounce_ms = 20; + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UTOUCH_init(Unit *unit) +{ + bool suc = true; + struct priv *priv = unit->data; + + unit->tick_interval = 1; // sample every 1 ms + + // copy from conf + priv->binary_debounce_ms = priv->cfg.binary_debounce_ms; + priv->binary_hysteresis = priv->cfg.binary_hysteresis; + + TRY(rsc_claim(unit, R_TSC)); + + // simple bound checks, just clamp without error + if (priv->cfg.charge_time > 16) priv->cfg.charge_time = 16; + if (priv->cfg.charge_time < 1) priv->cfg.charge_time = 1; + + if (priv->cfg.drain_time > 16) priv->cfg.drain_time = 16; + if (priv->cfg.drain_time < 1) priv->cfg.drain_time = 1; + + if (priv->cfg.spread_deviation > 128) priv->cfg.drain_time = 128; + + if (priv->cfg.ss_presc > 2) priv->cfg.ss_presc = 2; + if (priv->cfg.ss_presc < 1) priv->cfg.ss_presc = 1; + + if (priv->cfg.sense_timeout > 7) priv->cfg.sense_timeout = 7; + if (priv->cfg.sense_timeout < 1) priv->cfg.sense_timeout = 1; + + uint8_t tmppgpresc = priv->cfg.pg_presc; + if (tmppgpresc == 0) return E_BAD_CONFIG; + uint8_t pgpresc_reg = 0; + while ((tmppgpresc & 1) == 0 && tmppgpresc != 0) { + pgpresc_reg++; + tmppgpresc >>= 1; + } + if (tmppgpresc != 1 || pgpresc_reg > 7) { + dbg("Bad pgpresc"); + return E_BAD_CONFIG; // TODO better reporting + } + + if ((pgpresc_reg==0 && priv->cfg.drain_time<=2) || (pgpresc_reg==1 && priv->cfg.drain_time==0)) { + dbg("Illegal PGPSC vs CTPL"); + return E_BAD_CONFIG; + } + + // enable clock + hw_periph_clock_enable(TSC); + // reset + __HAL_RCC_TSC_FORCE_RESET(); + __HAL_RCC_TSC_RELEASE_RESET(); + + priv->all_channels_mask = 0; + + for (int gi = 0; gi < 8; gi++) { + const uint8_t cap = priv->cfg.group_scaps[gi]; + const uint8_t ch = priv->cfg.group_channels[gi]; + + if (cap == 0) { + if (ch != 0) { + dbg_touch("TSC group %d has no cap!", (int) (gi + 1)); + return E_BAD_CONFIG; + } + continue; + } + + if (ch == 0) continue; // if no channels, don't bother setting up anything + + if (cap != 2 && cap != 4 && cap != 8 && cap != 16) { + dbg_touch("TSC group %d has more than 1 cap!", (int) (gi + 1)); + return E_BAD_CONFIG; + } + + if (cap & ch) { + dbg_touch("TSC pin can't be both channel and cap! (gpr %d)", (int) (gi + 1)); + return E_BAD_CONFIG; + } + + // This is a loop through the pins in a group gi + int phasenum = 0; + for (int pi = 0; pi < 4; pi++) { + // pin numbers are 1-based in the config + const bool iscap = 0 != (cap & (2 << pi)); + const bool isch = 0 != (ch & (2 << pi)); + + if (!iscap && !isch) continue; + + Resource r = utouch_group_rscs[gi][pi]; + TRY(rsc_claim(unit, r)); + + GPIO_TypeDef *port; + uint32_t ll; + assert_param(hw_pinrsc2ll(r, &port, &ll)); + LL_GPIO_SetPinOutputType(port, ll, isch ? LL_GPIO_OUTPUT_PUSHPULL : LL_GPIO_OUTPUT_OPENDRAIN); + + // 7 and 8 (1-based) use AF1, else AF3 + TRY(hw_configure_gpiorsc_af(r, gi >= 6 ? LL_GPIO_AF_1 : LL_GPIO_AF_3)); + + uint32_t bit = (uint32_t) (1 << (gi * 4 + pi)); + + if (iscap) { + dbg_touch("TSC cap @ %s", rsc_get_name(r)); + // Sampling cap + TSC->IOSCR |= bit; + + // Disable pin hysteresis (causes noise) + TSC->IOHCR ^= bit; + } + else { + dbg_touch("TSC ch @ %s", rsc_get_name(r)); + + if (priv->cfg.interlaced) { + // interlaced - only update the mask beforehand + priv->all_channels_mask |= bit; + } else { + // channels are configured individually when read. + // we prepare bitmaps to use for the read groups (all can be read in at most 3 steps) + priv->channels_phase[phasenum] |= bit; // this is used for the channel selection register + priv->groups_phase[phasenum] |= 1 << gi; // this will be used for the group enable register, if all 0, this and any following phases are unused. + phasenum++; + } + } + } + } + + // common TSC config + TSC->CR = + ((priv->cfg.charge_time - 1) << TSC_CR_CTPH_Pos) | + ((priv->cfg.drain_time - 1) << TSC_CR_CTPL_Pos) | + ((priv->cfg.ss_presc - 1) << TSC_CR_SSPSC_Pos) | + (pgpresc_reg << TSC_CR_PGPSC_Pos) | + ((priv->cfg.sense_timeout - 1) << TSC_CR_MCV_Pos) | + TSC_CR_TSCE; + + if (priv->cfg.spread_deviation > 0) { + TSC->CR |= ((priv->cfg.spread_deviation - 1) << TSC_CR_SSD_Pos) | TSC_CR_SSE; + } + + dbg_touch("CR = %08x, ht is %d, lt is %d", (int)TSC->CR, + (int)priv->cfg.charge_time, + (int)priv->cfg.drain_time); + + // iofloat is used for discharging + + // Enable the interrupts + TSC->IER = TSC_IER_EOAIE | TSC_IER_MCEIE; + irqd_attach(TSC, UTOUCH_HandleIrq, unit); + + if (!priv->cfg.interlaced) { + dbg_touch("TSC phases:"); + for (int i = 0; i < 3; i++) { + priv->all_channels_mask |= priv->channels_phase[i]; + + dbg_touch(" %d: ch %08"PRIx32", g %02"PRIx32, + i + 1, + priv->channels_phase[i], + (uint32_t) priv->groups_phase[i]); + } + } + + priv->status = UTSC_STATUS_BUSY; // first loop ... + priv->next_phase = 0; + + // starts in the tick callback + + return E_SUCCESS; +} + + +/** Tear down the unit */ +void UTOUCH_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init peripherals + if (unit->status == E_SUCCESS) { + hw_periph_clock_disable(TSC); + // clear all registers to their default values + __HAL_RCC_TSC_FORCE_RESET(); + __HAL_RCC_TSC_RELEASE_RESET(); + + irqd_detach(TSC, UTOUCH_HandleIrq); + } + + // Release all resources, deinit pins + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); +} diff --git a/GexUnits/touch/_touch_internal.h b/GexUnits/touch/_touch_internal.h new file mode 100644 index 0000000..432c29a --- /dev/null +++ b/GexUnits/touch/_touch_internal.h @@ -0,0 +1,95 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#ifndef GEX_F072_TOUCH_INTERNAL_H +#define GEX_F072_TOUCH_INTERNAL_H + +#ifndef TOUCH_INTERNAL +#error bad include! +#endif + +#include "unit_base.h" + +#define TSC_DEBUG 0 + +#if TSC_DEBUG +#define dbg_touch(f,...) dbg(f,##__VA_ARGS__) +#else +#define dbg_touch(f,...) do{}while(0) +#endif + +enum utsc_status { + UTSC_STATUS_BUSY = 0, + UTSC_STATUS_READY = 1, + UTSC_STATUS_FAIL = 2 +}; + +/** Private data structure */ +struct priv { + // settings + struct { + uint8_t charge_time; // 1-16 -> 0..15 + uint8_t drain_time; // 1-16 -> 0..15 + uint8_t spread_deviation; // 1-128, 0=off ... 0-127, 0 sets 0 to SSE + uint8_t ss_presc; // 1-2 -> 0..1 + uint8_t pg_presc; // 1,2,4,8,16,32,64,128 -> 0..7 when writing to the periph + uint8_t sense_timeout; // 1-7 -> 0..6 hex when writing to the periph + // the schmitts must be disabled on all used channels, restored to 0xFFFF on deinit + uint8_t group_scaps[8]; + uint8_t group_channels[8]; + bool interlaced; + uint16_t binary_debounce_ms; + uint16_t binary_hysteresis; + } cfg; + + uint8_t next_phase; + uint8_t discharge_delay; + uint32_t channels_phase[3]; + uint8_t groups_phase[3]; + uint16_t readouts[32]; + int16_t bin_trig_cnt[32]; + uint16_t binary_debounce_ms; + uint16_t binary_hysteresis; + uint16_t binary_thr[32]; + uint32_t binary_active_bits; + uint32_t all_channels_mask; + uint32_t last_done_ms; + bool ongoing; + + enum utsc_status status; +} __attribute__((packed)); + +extern const char *utouch_group_labels[8]; +extern const Resource utouch_group_rscs[8][4]; + +/** Allocate data structure and set defaults */ +error_t UTOUCH_preInit(Unit *unit); + +/** Load from a binary buffer stored in Flash */ +void UTOUCH_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UTOUCH_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UTOUCH_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UTOUCH_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Finalize unit set-up */ +error_t UTOUCH_init(Unit *unit); + +/** Tear down the unit */ +void UTOUCH_deInit(Unit *unit); + +void UTOUCH_updateTick(Unit *unit); + +void UTOUCH_HandleIrq(void *arg); + +#endif //GEX_F072_TOUCH_INTERNAL_H diff --git a/GexUnits/touch/_touch_settings.c b/GexUnits/touch/_touch_settings.c new file mode 100644 index 0000000..eb80015 --- /dev/null +++ b/GexUnits/touch/_touch_settings.c @@ -0,0 +1,187 @@ +// +// Created by MightyPork on 2018/02/03. +// + +#include "platform.h" +#include "unit_base.h" + +#define TOUCH_INTERNAL +#include "_touch_internal.h" + +const char *utouch_group_labels[8] = { + "1:A0, 2:A1, 3:A2, 4:A3", + "1:A4, 2:A5, 3:A6, 4:A7", + "1:C5, 2:B0, 3:B1, 4:B2", + "1:A9, 2:A10, 3:A11, 4:A12", + "1:B3, 2:B4, 3:B6, 4:B7", + "1:B11, 2:B12, 3:B13, 4:B14", + "1:E2, 2:E3, 3:E4, 4:E5", + "1:D12, 2:D13, 3:D14, 4:D15", +}; + +const Resource utouch_group_rscs[8][4] = { + {R_PA0, R_PA1, R_PA2, R_PA3}, + {R_PA4, R_PA5, R_PA6, R_PA7}, + {R_PC5, R_PB0, R_PB1, R_PB2}, + {R_PA9, R_PA10, R_PA11, R_PA12}, + {R_PB3, R_PB4, R_PB6, R_PB7}, + {R_PB11, R_PB12, R_PB13, R_PB14}, + {R_PE2, R_PE3, R_PE4, R_PE5}, + {R_PD12, R_PD13, R_PD14, R_PD15}, +}; + +/** Load from a binary buffer stored in Flash */ +void UTOUCH_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->cfg.charge_time = pp_u8(pp); + priv->cfg.drain_time = pp_u8(pp); + priv->cfg.spread_deviation = pp_u8(pp); + priv->cfg.ss_presc = pp_u8(pp); + priv->cfg.pg_presc = pp_u8(pp); + priv->cfg.sense_timeout = pp_u8(pp); + pp_buf(pp, priv->cfg.group_scaps, 8); + pp_buf(pp, priv->cfg.group_channels, 8); + + if (version >= 1) { + priv->cfg.interlaced = pp_bool(pp); + } + + if (version >= 2) { + priv->cfg.binary_debounce_ms = pp_u16(pp); + priv->cfg.binary_hysteresis = pp_u16(pp); + } +} + +/** Write to a binary buffer for storing in Flash */ +void UTOUCH_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 2); // version + + pb_u8(pb, priv->cfg.charge_time); + pb_u8(pb, priv->cfg.drain_time); + pb_u8(pb, priv->cfg.spread_deviation); + pb_u8(pb, priv->cfg.ss_presc); + pb_u8(pb, priv->cfg.pg_presc); + pb_u8(pb, priv->cfg.sense_timeout); + pb_buf(pb, priv->cfg.group_scaps, 8); + pb_buf(pb, priv->cfg.group_channels, 8); + pb_bool(pb, priv->cfg.interlaced); + pb_u16(pb, priv->cfg.binary_debounce_ms); + pb_u16(pb, priv->cfg.binary_hysteresis); +} + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UTOUCH_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "charge-time")) { + priv->cfg.charge_time = cfg_u8_parse(value, &suc); + } + else if (streq(key, "drain-time")) { + priv->cfg.drain_time = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ss-deviation")) { + priv->cfg.spread_deviation = cfg_u8_parse(value, &suc); + } + else if (streq(key, "ss-clock-prediv")) { + priv->cfg.ss_presc = cfg_u8_parse(value, &suc); + } + else if (streq(key, "pg-clock-prediv")) { + priv->cfg.pg_presc = cfg_u8_parse(value, &suc); + } + else if (streq(key, "sense-timeout")) { + priv->cfg.sense_timeout = cfg_u8_parse(value, &suc); + } + else if (streq(key, "interlaced-pads")) { + priv->cfg.interlaced = cfg_bool_parse(value, &suc); + } + else if (streq(key, "btn-debounce")) { + priv->cfg.binary_debounce_ms = cfg_u16_parse(value, &suc); + } + else if (streq(key, "btn-hysteresis")) { + priv->cfg.binary_hysteresis = cfg_u16_parse(value, &suc); + } + else { + volatile char namebuf[10]; // must be volatile or gcc optimizes out the second compare and fucks it up + + for (int i = 0; i < 6; i++) { // skip 7,8 + SPRINTF(namebuf, "g%d_cap", i+1); + if (streq(key, namebuf)) { + priv->cfg.group_scaps[i] = (uint8_t) cfg_pinmask_parse(value, &suc); + goto matched; + } + + SPRINTF(namebuf, "g%d_ch", i+1); + if (streq(key, namebuf)) { + priv->cfg.group_channels[i] = (uint8_t) cfg_pinmask_parse(value, &suc); + goto matched; + } + } + + return E_BAD_KEY; + } + +matched: + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UTOUCH_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "This unit utilizes the touch sensing controller."); + iw_comment(iw, "See the reference manual for details about its function."); + iw_cmt_newline(iw); + + iw_comment(iw, "Pulse generator clock prescaller (1,2,4,...,128)"); + iw_entry_d(iw, "pg-clock-prediv", priv->cfg.pg_presc); + iw_comment(iw, "Sense pad charging time (1-16)"); + iw_entry_d(iw, "charge-time", priv->cfg.charge_time); + iw_comment(iw, "Charge transfer time (1-16)"); + iw_entry_d(iw, "drain-time", priv->cfg.drain_time); + iw_comment(iw, "Measurement timeout (1-7)"); + iw_entry_d(iw, "sense-timeout", priv->cfg.sense_timeout); + + iw_cmt_newline(iw); + iw_comment(iw, "Spread spectrum max deviation (0-128,0=off)"); + iw_entry_d(iw, "ss-deviation", priv->cfg.spread_deviation); + iw_comment(iw, "Spreading clock prescaller (1,2)"); + iw_entry_d(iw, "ss-clock-prediv", priv->cfg.ss_presc); + + iw_cmt_newline(iw); + iw_comment(iw, "Optimize for interlaced pads (individual sampling with others floating)"); + iw_entry_s(iw, "interlaced-pads", str_yn(priv->cfg.interlaced)); + + iw_cmt_newline(iw); + iw_comment(iw, "Button mode debounce (ms) and release hysteresis (lsb)"); + iw_entry_d(iw, "btn-debounce", priv->cfg.binary_debounce_ms); + iw_entry_d(iw, "btn-hysteresis", priv->cfg.binary_hysteresis); + + iw_cmt_newline(iw); + iw_comment(iw, "Each used group must have 1 sampling capacitor and 1-3 channels."); + iw_comment(iw, "Channels are numbered 1,2,3,4"); + iw_cmt_newline(iw); + + char namebuf[10]; + for (int i = 0; i < 6; i++) { // skip 7,8 + iw_commentf(iw, "Group%d - %s", i+1, utouch_group_labels[i]); + SPRINTF(namebuf, "g%d_cap", i+1); + iw_entry_s(iw, namebuf, cfg_pinmask_encode(priv->cfg.group_scaps[i], unit_tmp512, true)); + SPRINTF(namebuf, "g%d_ch", i+1); + iw_entry_s(iw, namebuf, cfg_pinmask_encode(priv->cfg.group_channels[i], unit_tmp512, true)); + } +} + diff --git a/GexUnits/touch/unit_touch.c b/GexUnits/touch/unit_touch.c new file mode 100644 index 0000000..a40e48f --- /dev/null +++ b/GexUnits/touch/unit_touch.c @@ -0,0 +1,136 @@ +// +// Created by MightyPork on 2017/11/25. +// + +#include "unit_base.h" +#include "unit_touch.h" + +#define TOUCH_INTERNAL +#include "_touch_internal.h" + +// ------------------------------------------------------------------------ + +enum TouchCmd_ { + CMD_READ=0, + CMD_SET_BIN_THR=1, + CMD_DISABLE_ALL_REPORTS=2, + CMD_SET_DEBOUNCE_TIME=3, + CMD_SET_HYSTERESIS=4, + CMD_GET_CH_COUNT=10, +}; + +/** Handle a request message */ +static error_t UTOUCH_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + struct priv* priv = unit->data; + PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); + + switch (command) { + /** + * read the current touch pad values (smaller = higher capacity) + * + * resp: a list of u16 (order: group and pin, ascending) + */ + case CMD_READ: + if (priv->status == UTSC_STATUS_BUSY) return E_BUSY; + if (priv->status == UTSC_STATUS_FAIL) return E_HW_TIMEOUT; + + for (int i = 0; i < 32; i++) { + if (priv->all_channels_mask & (1<readouts[i]); + } + } + com_respond_pb(frame_id, MSG_SUCCESS, &pb); + return E_SUCCESS; + + /** + * Set thresholds for the button mode. + * + * pld: a list of u16 for the enabled channels (order: group and pin, ascending) + */ + case CMD_SET_BIN_THR: + for (int i = 0; i < 32; i++) { + if (priv->all_channels_mask & (1<bin_trig_cnt[i] = 0; + priv->binary_thr[i] = pp_u16(pp); + if (priv->readouts[i] >= (priv->binary_thr[i] + priv->binary_hysteresis)) { + priv->binary_active_bits |= 1<binary_debounce_ms = pp_u16(pp); + return E_SUCCESS; + + /** + * Set hysteresis (replaces the default value from settings) + * + * Hysteresis is added to the threshold value for the switch-off level + * (switch-off happens when the measured value is exceeded - capacity of the pad drops) + * + * pld: hyst:u16 + */ + case CMD_SET_HYSTERESIS: + priv->binary_hysteresis = pp_u16(pp); + return E_SUCCESS; + + /** + * Disable button mode reports. This effectively sets all thresholds to 0, disabling checking. + */ + case CMD_DISABLE_ALL_REPORTS: + for (int i = 0; i < 32; i++) { + if (priv->all_channels_mask & (1<binary_thr[i] = 0; + priv->bin_trig_cnt[i] = 0; + } + } + priv->binary_active_bits = 0; + return E_SUCCESS; + + /** + * Get the number of configured touch pad channels + * + * resp: count:u8 + */ + case CMD_GET_CH_COUNT:; + uint8_t nb = 0; + for (int i = 0; i < 32; i++) { + if (priv->all_channels_mask & (1<data; + + uint32_t t_start = HAL_GetTick(); + while (len > 0) { + // this should be long enough even for the slowest bitrates and 512 bytes + if (HAL_GetTick() - t_start > 5000) { + return E_HW_TIMEOUT; + } + + uint16_t chunk = UUSART_DMA_TxQueue(priv, buffer, (uint16_t) len); + + buffer += chunk; + len -= chunk; + + // We give up control if there's another thread waiting and this isn't the last cycle + if (len > 0) { + osThreadYield(); + } + } + + return E_SUCCESS; +} + + +error_t UU_USART_WriteSync(Unit *unit, const uint8_t *buffer, uint32_t len) +{ + CHECK_TYPE(unit, &UNIT_USART); + struct priv *priv = unit->data; + + TRY(UU_USART_Write(unit, buffer, len)); + + // Now wait for the last DMA to complete + uint32_t t_start = HAL_GetTick(); + while (priv->tx_dma_busy) { + if (HAL_GetTick() - t_start > 1000) { + return E_HW_TIMEOUT; + } + } + + return E_SUCCESS; +} diff --git a/GexUnits/usart/_usart_dmas.c b/GexUnits/usart/_usart_dmas.c new file mode 100644 index 0000000..39de18e --- /dev/null +++ b/GexUnits/usart/_usart_dmas.c @@ -0,0 +1,444 @@ +// +// Created by MightyPork on 2018/01/14. +// + +#include "platform.h" +#include "irq_dispatcher.h" +#include "unit_base.h" + +#define UUSART_INTERNAL +#include "_usart_internal.h" + +static void UUSART_DMA_RxHandler(void *arg); +static void UUSART_DMA_TxHandler(void *arg); + +#if UUSART_DEBUG +#define dbg_uusart(fmt, ...) dbg(fmt, ##__VA_ARGS__) +#else +#define dbg_uusart(fmt, ...) +#endif + +error_t UUSART_ClaimDMAs(Unit *unit) +{ + error_t rv; + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + priv->dma = DMA1; + + switch (priv->periph_num) { + /* USART1 */ + case 1: + // TX + rv = rsc_claim(unit, R_DMA1_2); + if (rv == E_SUCCESS) { + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART1TX_RMP_DMA1CH2); + priv->dma_tx = DMA1_Channel2; + priv->dma_tx_chnum = 2; + } else { + // try the remap + TRY(rsc_claim(unit, R_DMA1_4)); + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART1TX_RMP_DMA1CH4); + priv->dma_tx = DMA1_Channel4; + priv->dma_tx_chnum = 4; + } + + // RX + rv = rsc_claim(unit, R_DMA1_3); + if (rv == E_SUCCESS) { + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART1RX_RMP_DMA1CH3); + priv->dma_rx = DMA1_Channel3; + priv->dma_rx_chnum = 3; + } else { + // try the remap + TRY(rsc_claim(unit, R_DMA1_5)); + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART1RX_RMP_DMA1CH5); + priv->dma_rx = DMA1_Channel5; + priv->dma_rx_chnum = 5; + } + break; + + /* USART2 */ + case 2: + // RX,TX + rv = rsc_claim_range(unit, R_DMA1_4, R_DMA1_5); + if (rv == E_SUCCESS) { + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART2_RMP_DMA1CH54); + priv->dma_tx = DMA1_Channel4; + priv->dma_rx = DMA1_Channel5; + priv->dma_tx_chnum = 4; + priv->dma_rx_chnum = 5; + } else { + // try the remap + TRY(rsc_claim_range(unit, R_DMA1_6, R_DMA1_7)); + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART2_RMP_DMA1CH67); + priv->dma_tx = DMA1_Channel7; + priv->dma_rx = DMA1_Channel6; + priv->dma_tx_chnum = 7; + priv->dma_rx_chnum = 6; + } + break; + + /* USART3 */ + case 3: + // RX,TX + rv = rsc_claim_range(unit, R_DMA1_6, R_DMA1_7); + if (rv == E_SUCCESS) { + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART3_RMP_DMA1CH67); + priv->dma_tx = DMA1_Channel7; + priv->dma_rx = DMA1_Channel6; + priv->dma_tx_chnum = 7; + priv->dma_rx_chnum = 6; + } else { + // try the remap + TRY(rsc_claim_range(unit, R_DMA1_2, R_DMA1_3)); + LL_SYSCFG_SetRemapDMA_USART(LL_SYSCFG_USART3_RMP_DMA1CH32); + priv->dma_tx = DMA1_Channel2; + priv->dma_rx = DMA1_Channel3; + priv->dma_tx_chnum = 2; + priv->dma_rx_chnum = 3; + } + break; + + /* USART4 */ + case 4: + // RX,TX + TRY(rsc_claim_range(unit, R_DMA1_6, R_DMA1_7)); + priv->dma_tx = DMA1_Channel7; + priv->dma_rx = DMA1_Channel6; + priv->dma_tx_chnum = 7; + priv->dma_rx_chnum = 6; + break; + + default: + trap("Missing DMA mapping for USART%d", (int)priv->periph_num); + } + + dbg_uusart("USART %d - selected DMA ch Tx(%d), Rx(%d)", priv->periph_num, priv->dma_tx_chnum, priv->dma_rx_chnum); + + return E_SUCCESS; +} + +error_t UUSART_SetupDMAs(Unit *unit) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + priv->rx_buffer = malloc_ck(UUSART_RXBUF_LEN); + if (NULL == priv->rx_buffer) return E_OUT_OF_MEM; + + priv->tx_buffer = malloc_ck(UUSART_TXBUF_LEN); + if (NULL == priv->tx_buffer) return E_OUT_OF_MEM; + + // Those must be aligned to a word boundary for the DMAs to work. + // Any well-behaved malloc impl should do this correctly. + assert_param(((uint32_t)priv->rx_buffer & 3) == 0); + assert_param(((uint32_t)priv->tx_buffer & 3) == 0); + + priv->rx_buf_readpos = 0; + + LL_DMA_InitTypeDef init; + + // Transmit buffer + { + LL_DMA_StructInit(&init); + init.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + init.Mode = LL_DMA_MODE_NORMAL; + + init.PeriphOrM2MSrcAddress = (uint32_t) &priv->periph->TDR; + init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE; + init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + + init.MemoryOrM2MDstAddress = (uint32_t) priv->tx_buffer; + init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; + init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + + assert_param(SUCCESS == LL_DMA_Init(priv->dma, priv->dma_tx_chnum, &init)); + + irqd_attach(priv->dma_tx, UUSART_DMA_TxHandler, unit); + // Interrupt on transfer complete + LL_DMA_EnableIT_TC(priv->dma, priv->dma_tx_chnum); + } + + // Receive buffer + { + LL_DMA_StructInit(&init); + init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; + + init.Mode = LL_DMA_MODE_CIRCULAR; + init.NbData = UUSART_RXBUF_LEN; + + init.PeriphOrM2MSrcAddress = (uint32_t) &priv->periph->RDR; + init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE; + init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + + init.MemoryOrM2MDstAddress = (uint32_t) priv->rx_buffer; + init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; + init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + + assert_param(SUCCESS == LL_DMA_Init(priv->dma, priv->dma_rx_chnum, &init)); + + irqd_attach(priv->dma_rx, UUSART_DMA_RxHandler, unit); + // Interrupt on transfer 1/2 and complete + // We will capture the first and second half and send it while the other half is being filled. + LL_DMA_EnableIT_HT(priv->dma, priv->dma_rx_chnum); + LL_DMA_EnableIT_TC(priv->dma, priv->dma_rx_chnum); + } + + LL_DMA_EnableChannel(priv->dma, priv->dma_rx_chnum); + LL_DMA_EnableChannel(priv->dma, priv->dma_tx_chnum); + + return E_SUCCESS; +} + +/** + * Handler for the Rx DMA half or full interrupt + * @param arg - unit instance + */ +static void UUSART_DMA_RxHandler(void *arg) +{ + Unit *unit = arg; + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + const uint32_t isrsnapshot = priv->dma->ISR; + + if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_rx_chnum)) { + bool tc = LL_DMA_IsActiveFlag_TC(isrsnapshot, priv->dma_rx_chnum); + bool ht = LL_DMA_IsActiveFlag_HT(isrsnapshot, priv->dma_rx_chnum); + + // Here we have to either copy it somewhere else, or notify another thread (queue?) + // that the data is ready for reading + + if (ht) { + uint16_t end = (uint16_t) UUSART_RXBUF_LEN / 2; + UUSART_DMA_HandleRxFromIRQ(unit, end); + LL_DMA_ClearFlag_HT(priv->dma, priv->dma_rx_chnum); + } + + if (tc) { + uint16_t end = (uint16_t) UUSART_RXBUF_LEN; + UUSART_DMA_HandleRxFromIRQ(unit, end); + LL_DMA_ClearFlag_TC(priv->dma, priv->dma_rx_chnum); + } + + if (LL_DMA_IsActiveFlag_TE(isrsnapshot, priv->dma_rx_chnum)) { + // this shouldn't happen + dbg("USART DMA TE!"); + LL_DMA_ClearFlag_TE(priv->dma, priv->dma_rx_chnum); + } + } +} + +/** + * Start sending a chunk of data. + * This must be called when the DMA is completed. + * + * @param priv + */ +static void UUSART_DMA_TxStart(struct priv *priv) +{ + priv->tx_dma_busy = true; + assert_param(priv->dma_tx->CNDTR == 0); + + dbg_uusart("DMA_TxStart (nr %d, nw %d)", (int)priv->tx_buf_nr, (int)priv->tx_buf_nw); + + uint16_t nr = priv->tx_buf_nr; + uint16_t nw = priv->tx_buf_nw; + + if (nr == nw) { + dbg_uusart("remain=0,do nothing"); + return; + } // do nothing if we're done + + uint8_t chunk = priv->tx_buffer[nr++]; + //nr += (uint16_t) (4 - (nr & 0b11)); + if (chunk == 0) { + // wrap-around + chunk = priv->tx_buffer[0]; + nr = 1; + assert_param(nr < nw); + } + + // nr was advanced by the lpad preamble + priv->tx_buf_nr = nr; + priv->tx_buf_chunk = chunk; // will be further moved by 'chunk' bytes when dma completes + + dbg_uusart("# TX: chunk start %d, len %d", (int)nr, (int)chunk); +//#if UUSART_DEBUG +// PUTS(">"); PUTSN((char *) (priv->tx_buffer + nr), chunk); PUTS("<"); +// PUTNL(); +//#endif + + LL_DMA_DisableChannel(priv->dma, priv->dma_tx_chnum); + { + LL_DMA_ClearFlags(priv->dma, priv->dma_tx_chnum); + LL_DMA_SetMemoryAddress(priv->dma, priv->dma_tx_chnum, (uint32_t) (priv->tx_buffer + nr)); + LL_DMA_SetDataLength(priv->dma, priv->dma_tx_chnum, chunk); + LL_USART_ClearFlag_TC(priv->periph); + } + LL_DMA_EnableChannel(priv->dma, priv->dma_tx_chnum); +} + +COMPILER_ASSERT(UUSART_TXBUF_LEN <= 256); // more would break the "len tag" algorithm + +/** + * Put data on the queue. Only a part may be sent due to a buffer size limit. + * + * @param priv + * @param buffer - buffer to send + * @param len - buffer size + * @return number of bytes that were really written (from the beginning) + */ +uint16_t UUSART_DMA_TxQueue(struct priv *priv, const uint8_t *buffer, uint16_t len) +{ + const uint16_t nr = priv->tx_buf_nr; + uint16_t nw = priv->tx_buf_nw; + + // shortcut for checking a completely full buffer + if (nw == nr-1 || (nr==0&&nw==UUSART_TXBUF_LEN-1)) { + dbg_uusart("Buffer full, cant queue"); + return 0; + } + + dbg_uusart("\r\nQueue.."); + + uint16_t used = 0; + if (nr == nw) { + used = 0; + } else if (nw > nr) { // simple linear + used = (uint16_t) (nw - nr); + } else if (nw < nr) { // wrapped + used = (uint16_t) ((UUSART_TXBUF_LEN - nr) + nw); + } + + dbg_uusart("Trying to send buffer of len %d", (int)len); + uint16_t avail = (const uint16_t) (UUSART_TXBUF_LEN - 1 - used); + dbg_uusart("nr %d, nw %d, used %d, avail %d", (int)nr, (int)nw, (int)used, (int)avail); + + // hack to avoid too large chunks (we use 1 byte to store chunk size) + if (avail > 255) avail = 255; + + uint8_t written = 0; + + // this avoids attempting to write if we don't have space + if (avail <= 5) { + dbg_uusart("No space (only %d)", (int) avail); + return written; + } + + int cnt = 0; + while (avail > 0 && written < len) { + assert_param(cnt < 2); // if more than two, we have a bug and it's repeating infinitely + + cnt++; + // Padding with chunk information (1 byte: length) - for each chunk + const uint8_t lpad = 1; + + // Chunk can go max to the end of the buffer + uint8_t chunk = (uint8_t) MIN((len-written) + lpad, UUSART_TXBUF_LEN - nw); + if (chunk > avail) chunk = (uint8_t) avail; + + dbg_uusart("nw %d, raw available chunk %d", (int) nw, (int)chunk); + if (chunk < lpad + 1) { + // write 0 to indicate a wrap-around + dbg_uusart("Wrap-around marker at offset %d", (int) nw); + priv->tx_buffer[nw] = 0; + nw = 0; + } + else { + // enough space for a preamble + some data + dbg_uusart("Preamble of %d bytes at offset %d", (int) lpad, (int) nw); + priv->tx_buffer[nw] = (uint8_t) (chunk - lpad); + nw += lpad; + uint8_t datachunk = (uint8_t) (chunk - lpad); + dbg_uusart("Datachunk len %d at offset %d", (int) datachunk, (int) nw); +//#if UUSART_DEBUG +// PUTS("mcpy src >"); PUTSN((char *) (buffer), datachunk); PUTS("<\r\n"); +//#endif + memcpy((uint8_t *) (priv->tx_buffer + nw), buffer, datachunk); +//#if UUSART_DEBUG +// PUTS("mcpy dst >"); PUTSN((char *) (priv->tx_buffer + nw), datachunk); PUTS("<\r\n"); +//#endif + buffer += datachunk; + nw += datachunk; + written += datachunk; + if (nw == UUSART_TXBUF_LEN) nw = 0; + } + avail -= chunk; + dbg_uusart(". end of loop, avail is %d", (int)avail); + } + + { + dbg_uusart("Write done -> nr %d, nw %d", (int) nr, (int) nw); + + // FIXME a potential race condition can happen here (but it's unlikely) + + priv->tx_buf_nw = nw; + + if (!priv->tx_dma_busy) { + dbg_uusart("Write done, requesting DMA."); + UUSART_DMA_TxStart(priv); + } + else { + dbg_uusart("DMA in progress, not requesting"); + } + } + + return written; +} + +/** + * Handler for the Tx DMA - completion interrupt + * @param arg - unit instance + */ +static void UUSART_DMA_TxHandler(void *arg) +{ + Unit *unit = arg; + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + uint32_t isrsnapshot = priv->dma->ISR; + if (LL_DMA_IsActiveFlag_TC(isrsnapshot, priv->dma_tx_chnum)) { + // chunk Tx is finished + dbg_uusart("~ DMA tx done, nr %d, nw %d, chunk %d", (int)priv->tx_buf_nr, (int)priv->tx_buf_nw, (int)priv->tx_buf_chunk); + + priv->tx_buf_nr += priv->tx_buf_chunk; + if (UUSART_TXBUF_LEN == priv->tx_buf_nr) priv->tx_buf_nr = 0; + priv->tx_buf_chunk = 0; + + LL_DMA_ClearFlag_TC(priv->dma, priv->dma_tx_chnum); + + // Wait for TC + while (!LL_USART_IsActiveFlag_TC(priv->periph)); // TODO timeout + + // start the next chunk + if (priv->tx_buf_nr != priv->tx_buf_nw) { + dbg_uusart(" Asking for more, if any"); + UUSART_DMA_TxStart(priv); + } else { + priv->tx_dma_busy = false; + } + } +} + + +void UUSART_DeInitDMAs(Unit *unit) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + irqd_detach(priv->dma_tx, UUSART_DMA_TxHandler); + irqd_detach(priv->dma_rx, UUSART_DMA_RxHandler); + + LL_DMA_DeInit(priv->dma, priv->dma_rx_chnum); + LL_DMA_DeInit(priv->dma, priv->dma_tx_chnum); + + free_ck(priv->rx_buffer); + free_ck(priv->tx_buffer); +} diff --git a/GexUnits/usart/_usart_init.c b/GexUnits/usart/_usart_init.c new file mode 100644 index 0000000..306318d --- /dev/null +++ b/GexUnits/usart/_usart_init.c @@ -0,0 +1,344 @@ +// +// Created by MightyPork on 2018/01/14. +// +#include "platform.h" +#include "unit_base.h" + +#define UUSART_INTERNAL +#include "_usart_internal.h" + +extern error_t UUSART_ClaimDMAs(Unit *unit); +extern error_t UUSART_SetupDMAs(Unit *unit); +extern void UUSART_DeInitDMAs(Unit *unit); + +/** Allocate data structure and set defaults */ +error_t UUSART_preInit(Unit *unit) +{ + struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); + if (priv == NULL) return E_OUT_OF_MEM; + + // some defaults + priv->periph_num = 1; + priv->remap = 0; + + priv->baudrate = 115200; + priv->parity = 0; //!< 0-none, 1-odd, 2-even + priv->stopbits = 1; //!< 0-half, 1-one, 2-1.5, 3-two + priv->direction = UUSART_DIRECTION_RXTX; // RXTX + + priv->hw_flow_control = false; + priv->clock_output = false; + priv->cpol = 0; + priv->cpha = 0; + priv->lsb_first = true; // LSB first is default for UART + priv->width = 8; + + priv->data_inv = false; + priv->rx_inv = false; + priv->tx_inv = false; + + priv->de_output = false; + priv->de_polarity = 1; // active high + // this should equal to a half-byte length when oversampling by 16 is used (default) + priv->de_assert_time = 8; + priv->de_clear_time = 8; + + return E_SUCCESS; +} + +/** Claim the peripheral and assign priv->periph */ +static inline error_t UUSART_claimPeriph(Unit *unit) +{ + struct priv *priv = unit->data; + + if (!(priv->periph_num >= 1 && priv->periph_num <= 5)) { + dbg("!! Bad USART periph"); + return E_BAD_CONFIG; + } + + // assign and claim the peripheral + if (priv->periph_num == 1) { + TRY(rsc_claim(unit, R_USART1)); + priv->periph = USART1; + } + else if (priv->periph_num == 2) { + TRY(rsc_claim(unit, R_USART2)); + priv->periph = USART2; + } + else if (priv->periph_num == 3) { + TRY(rsc_claim(unit, R_USART3)); + priv->periph = USART3; + } +#if defined(USART4) + else if (priv->periph_num == 4) { + TRY(rsc_claim(unit, R_USART4)); + priv->periph = USART4; + } +#endif +#if defined(USART5) + else if (priv->periph_num == 5) { + TRY(rsc_claim(unit, R_USART5)); + priv->periph = USART5; + } +#endif + else return E_BAD_CONFIG; + + TRY(UUSART_ClaimDMAs(unit)); + + return E_SUCCESS; +} + +/** Claim and configure GPIOs used */ +static inline error_t UUSART_configPins(Unit *unit) +{ + struct priv *priv = unit->data; + // This is written for F072, other platforms will need adjustments + + // Configure UART pins (AF) + +#define want_ck_pin(priv) ((priv)->clock_output) +#define want_tx_pin(priv) (bool)((priv)->direction & 2) +#define want_rx_pin(priv) (bool)((priv)->direction & 1) +#define want_cts_pin(priv) ((priv)->hw_flow_control==2 || (priv)->hw_flow_control==3) +#define want_rts_pin(priv) ((priv)->de_output || (priv)->hw_flow_control==1 || (priv)->hw_flow_control==3) + + /* List of required pins based on the user config */ + bool pins_wanted[5] = { + want_ck_pin(priv), + want_tx_pin(priv), + want_rx_pin(priv), + want_cts_pin(priv), + want_rts_pin(priv) + }; + +#if STM32F072xB + + const struct PinAF *mappings = NULL; + + // TODO adjust this, possibly remove / split to individual pin config for .. + // the final board + + const struct PinAF mapping_1_0[5] = { + {'A', 8, LL_GPIO_AF_1}, // CK + {'A', 9, LL_GPIO_AF_1}, // TX + {'A', 10, LL_GPIO_AF_1}, // RX + {'A', 11, LL_GPIO_AF_1}, // CTS - collides with USB + {'A', 12, LL_GPIO_AF_1}, // RTS - collides with USB + }; + + const struct PinAF mapping_1_1[5] = { + {'A', 8, LL_GPIO_AF_1}, // CK* + {'B', 6, LL_GPIO_AF_1}, // TX + {'B', 7, LL_GPIO_AF_1}, // RX + {'A', 11, LL_GPIO_AF_1}, // CTS* - collides with USB + {'A', 12, LL_GPIO_AF_1}, // RTS* - collides with USB + }; + + const struct PinAF mapping_2_0[5] = { + {'A', 4, LL_GPIO_AF_1}, // CK + {'A', 2, LL_GPIO_AF_1}, // TX + {'A', 3, LL_GPIO_AF_1}, // RX + {'A', 0, LL_GPIO_AF_1}, // CTS + {'A', 1, LL_GPIO_AF_1}, // RTS + }; + + const struct PinAF mapping_2_1[5] = { + {'A', 4, LL_GPIO_AF_1}, // CK* + {'A', 14, LL_GPIO_AF_1}, // TX + {'A', 15, LL_GPIO_AF_1}, // RX + {'A', 0, LL_GPIO_AF_1}, // CTS* + {'A', 1, LL_GPIO_AF_1}, // RTS* + }; + + const struct PinAF mapping_3_0[5] = { + {'B', 12, LL_GPIO_AF_4}, // CK + {'B', 10, LL_GPIO_AF_4}, // TX + {'B', 11, LL_GPIO_AF_4}, // RX + {'B', 13, LL_GPIO_AF_4}, // CTS + {'B', 14, LL_GPIO_AF_4}, // RTS + }; + + const struct PinAF mapping_4_0[5] = { + {'C', 12, LL_GPIO_AF_0}, // CK + {'A', 0, LL_GPIO_AF_4}, // TX + {'A', 1, LL_GPIO_AF_4}, // RX + {'B', 7, LL_GPIO_AF_4}, // CTS + {'A', 15, LL_GPIO_AF_4}, // RTS + }; + + const struct PinAF mapping_4_1[5] = { + {'C', 12, LL_GPIO_AF_0}, // CK* + {'C', 10, LL_GPIO_AF_0}, // TX + {'C', 11, LL_GPIO_AF_0}, // RX + {'B', 7, LL_GPIO_AF_4}, // CTS* + {'A', 15, LL_GPIO_AF_4}, // RTS* + }; + + if (priv->periph_num == 1) { + // USART1 + if (priv->remap == 0) mappings = &mapping_1_0[0]; + else if (priv->remap == 1) mappings = &mapping_1_1[0]; + else return E_BAD_CONFIG; + } + else if (priv->periph_num == 2) { + // USART2 + if (priv->remap == 0) mappings = &mapping_2_0[0]; + else if (priv->remap == 1) mappings = &mapping_2_1[0]; + else return E_BAD_CONFIG; + } + else if (priv->periph_num == 3) { + // USART3 + if (priv->remap == 0) mappings = &mapping_3_0[0]; + else return E_BAD_CONFIG; + } + else if (priv->periph_num == 4) { + // USART3 + if (priv->remap == 0) mappings = &mapping_4_0[0]; + else if (priv->remap == 1) mappings = &mapping_4_1[0]; + else return E_BAD_CONFIG; + } + else return E_BAD_CONFIG; + + // Apply mappings based on the 'wanted' table + for (int i = 0; i < 5; i++) { + if (pins_wanted[i]) { + if (mappings[i].port == 0) return E_BAD_CONFIG; + TRY(rsc_claim_pin(unit, mappings[i].port, mappings[i].pin)); + TRY(hw_configure_gpio_af(mappings[i].port, mappings[i].pin, mappings[i].af)); + } + } + +#elif GEX_PLAT_F103_BLUEPILL + #error "NO IMPL" +#elif GEX_PLAT_F303_DISCOVERY + #error "NO IMPL" +#elif GEX_PLAT_F407_DISCOVERY + #error "NO IMPL" +#else + #error "BAD PLATFORM!" +#endif + + return E_SUCCESS; +} + +/** Finalize unit set-up */ +error_t UUSART_init(Unit *unit) +{ + struct priv *priv = unit->data; + + TRY(UUSART_claimPeriph(unit)); + TRY(UUSART_configPins(unit)); + + // --- Configure the peripheral --- + + // Enable clock for the peripheral used + hw_periph_clock_enable(priv->periph); + + LL_USART_Disable(priv->periph); + { + LL_USART_DeInit(priv->periph); + LL_USART_SetBaudRate(priv->periph, PLAT_APB1_HZ, LL_USART_OVERSAMPLING_16, priv->baudrate); + + LL_USART_SetParity(priv->periph, + priv->parity == 0 ? LL_USART_PARITY_NONE : + priv->parity == 1 ? LL_USART_PARITY_ODD + : LL_USART_PARITY_EVEN); + + LL_USART_SetStopBitsLength(priv->periph, + priv->stopbits == 0 ? LL_USART_STOPBITS_0_5 : + priv->stopbits == 1 ? LL_USART_STOPBITS_1 : + priv->stopbits == 2 ? LL_USART_STOPBITS_1_5 + : LL_USART_STOPBITS_2); + + LL_USART_SetTransferDirection(priv->periph, + (priv->direction == UUSART_DIRECTION_RX) ? LL_USART_DIRECTION_RX : + (priv->direction == UUSART_DIRECTION_TX) ? LL_USART_DIRECTION_TX + : LL_USART_DIRECTION_TX_RX); + + LL_USART_SetHWFlowCtrl(priv->periph, + priv->hw_flow_control == 0 ? LL_USART_HWCONTROL_NONE : + priv->hw_flow_control == 1 ? LL_USART_HWCONTROL_RTS : + priv->hw_flow_control == 2 ? LL_USART_HWCONTROL_CTS + : LL_USART_HWCONTROL_RTS_CTS); + + LL_USART_ConfigClock(priv->periph, + priv->cpha ? LL_USART_PHASE_2EDGE : LL_USART_PHASE_1EDGE, + priv->cpol ? LL_USART_POLARITY_HIGH : LL_USART_POLARITY_LOW, + true); // clock on last bit - TODO configurable? + + if (priv->clock_output) + LL_USART_EnableSCLKOutput(priv->periph); + else + LL_USART_DisableSCLKOutput(priv->periph); + + LL_USART_SetTransferBitOrder(priv->periph, + priv->lsb_first ? LL_USART_BITORDER_LSBFIRST + : LL_USART_BITORDER_MSBFIRST); + + LL_USART_SetDataWidth(priv->periph, + priv->width == 7 ? LL_USART_DATAWIDTH_7B : + priv->width == 8 ? LL_USART_DATAWIDTH_8B + : LL_USART_DATAWIDTH_9B); + + LL_USART_SetBinaryDataLogic(priv->periph, + priv->data_inv ? LL_USART_BINARY_LOGIC_NEGATIVE + : LL_USART_BINARY_LOGIC_POSITIVE); + + LL_USART_SetRXPinLevel(priv->periph, priv->rx_inv ? LL_USART_RXPIN_LEVEL_INVERTED + : LL_USART_RXPIN_LEVEL_STANDARD); + + LL_USART_SetTXPinLevel(priv->periph, priv->tx_inv ? LL_USART_TXPIN_LEVEL_INVERTED + : LL_USART_TXPIN_LEVEL_STANDARD); + + if (priv->de_output) + LL_USART_EnableDEMode(priv->periph); + else + LL_USART_DisableDEMode(priv->periph); + + LL_USART_SetDESignalPolarity(priv->periph, + priv->de_polarity ? LL_USART_DE_POLARITY_HIGH + : LL_USART_DE_POLARITY_LOW); + + LL_USART_SetDEAssertionTime(priv->periph, priv->de_assert_time); + LL_USART_SetDEDeassertionTime(priv->periph, priv->de_clear_time); + + // Prepare for DMA + LL_USART_ClearFlag_TC(priv->periph); + LL_USART_EnableDMAReq_RX(priv->periph); + LL_USART_EnableDMAReq_TX(priv->periph); + } + LL_USART_Enable(priv->periph); + + // modifies some usart registers that can't be modified when enabled + TRY(UUSART_SetupDMAs(unit)); + + // timeout based on the baudrate + unit->tick_interval = (uint16_t) ((50 * 1000) / priv->baudrate); // receive timeout (ms) + if (unit->tick_interval < 5) unit->tick_interval = 5; + + return E_SUCCESS; +} + +/** Tear down the unit */ +void UUSART_deInit(Unit *unit) +{ + struct priv *priv = unit->data; + + // de-init the pins & peripheral only if inited correctly + if (unit->status == E_SUCCESS) { + assert_param(priv->periph); + LL_USART_DeInit(priv->periph); + + // Disable clock + hw_periph_clock_disable(priv->periph); + + UUSART_DeInitDMAs(unit); + } + + // Release all resources + rsc_teardown(unit); + + // Free memory + free_ck(unit->data); + unit->data = NULL; +} diff --git a/GexUnits/usart/_usart_internal.h b/GexUnits/usart/_usart_internal.h new file mode 100644 index 0000000..7b4584a --- /dev/null +++ b/GexUnits/usart/_usart_internal.h @@ -0,0 +1,118 @@ +// +// Created by MightyPork on 2018/01/14. +// +// Internal defines and consts used by the USART unit. +// Can be included only by the USART c files. +// + +#ifndef GEX_F072_UUSART_INTERNAL_H +#define GEX_F072_UUSART_INTERNAL_H + +#ifndef UUSART_INTERNAL +#error "Bad include" +#endif + +#include "platform.h" + +#define UUSART_RXBUF_LEN 128 +#define UUSART_TXBUF_LEN 128 + +#define UUSART_DIRECTION_RX 1 +#define UUSART_DIRECTION_TX 2 +#define UUSART_DIRECTION_RXTX 3 + +/** Private data structure */ +struct priv { + uint8_t periph_num; //!< 1-6 + uint8_t remap; //!< UART remap option + + uint32_t baudrate; //!< baud rate + uint8_t parity; //!< 0-none, 1-odd, 2-even + uint8_t stopbits; //!< 0-half, 1-one, 2-one-and-half, 3-two (halves - 1) + uint8_t direction; //!< 1-RX, 2-TX, 3-RXTX + + uint8_t hw_flow_control; //!< HW flow control 0-none, 1-RTC, 2-CTS, 3-full + bool clock_output; //!< Output serial clock + bool cpol; //!< clock CPOL setting + bool cpha; //!< clock CPHA setting + bool lsb_first; //!< bit order + uint8_t width; //!< word width - 7, 8, 9 (this includes parity) + + bool data_inv; //!< Invert data bytes + bool rx_inv; //!< Invert the RX pin levels + bool tx_inv; //!< Invert the TX pin levels + + bool de_output; //!< Generate the Driver Enable signal for RS485 + bool de_polarity; //!< DE active level + uint8_t de_assert_time; //!< Time to assert the DE signal before transmit + uint8_t de_clear_time; //!< Time to clear the DE signal after transmit + + USART_TypeDef *periph; //!< USART peripheral + + DMA_TypeDef *dma; //!< DMA peripheral + uint8_t dma_rx_chnum; //!< DMA rx channel number (resolved dynamically based on availability) + uint8_t dma_tx_chnum; //!< DMA tx channel number (resolved dynamically based on availability) + DMA_Channel_TypeDef *dma_rx; //!< DMA rx channel instance + DMA_Channel_TypeDef *dma_tx; //!< DMA tx channel instance + + // DMA stuff + volatile uint8_t *rx_buffer; //!< Receive buffer (malloc'd). Has configured TC and TH interrupts. + volatile uint16_t rx_buf_readpos; //!< Start of the next read (sending to USB) + volatile uint16_t rx_last_dmapos; //!< Last position of the DMA cyclic write. Used to detect timeouts and for partial data capture from the buffer + + volatile uint8_t *tx_buffer; //!< Transmit buffer (malloc'd) + volatile uint16_t tx_buf_nr; //!< Next Read index + volatile uint16_t tx_buf_nw; //!< Next Write index + volatile uint16_t tx_buf_chunk; //!< Size of the currently being transmitted chunk (for advancing the pointers) + volatile bool tx_dma_busy; //!< Flag that the Tx DMA request is ongoing +}; + +// ------------------------------------------------------------------------ + +/** Load from a binary buffer stored in Flash */ +void UUSART_loadBinary(Unit *unit, PayloadParser *pp); + +/** Write to a binary buffer for storing in Flash */ +void UUSART_writeBinary(Unit *unit, PayloadBuilder *pb); + +// ------------------------------------------------------------------------ + +/** Parse a key-value pair from the INI file */ +error_t UUSART_loadIni(Unit *unit, const char *key, const char *value); + +/** Generate INI file section for the unit */ +void UUSART_writeIni(Unit *unit, IniWriter *iw); + +// ------------------------------------------------------------------------ + +/** Allocate data structure and set defaults */ +error_t UUSART_preInit(Unit *unit); + +/** Tear down the unit */ +void UUSART_deInit(Unit *unit); + +/** Finalize unit set-up */ +error_t UUSART_init(Unit *unit); + +/** + * Handle received data (we're inside the IRQ) + * + * @param unit - handled unit + * @param endpos - end position in the buffer + */ +void UUSART_DMA_HandleRxFromIRQ(Unit *unit, uint16_t endpos); + +/** + * Put data on the queue. Only a part may be sent due to a buffer size limit. + * + * @param priv + * @param buffer - buffer to send + * @param len - buffer size + * @return number of bytes that were really written (from the beginning) + */ +uint16_t UUSART_DMA_TxQueue(struct priv *priv, const uint8_t *buffer, uint16_t len); + +// ------------------------------------------------------------------------ + + +#endif //GEX_F072_UUSART_INTERNAL_H diff --git a/GexUnits/usart/_usart_settings.c b/GexUnits/usart/_usart_settings.c new file mode 100644 index 0000000..5519a75 --- /dev/null +++ b/GexUnits/usart/_usart_settings.c @@ -0,0 +1,233 @@ +// +// Created by MightyPork on 2018/01/14. +// + +#include "platform.h" +#include "unit_base.h" +#include "unit_usart.h" + +#define UUSART_INTERNAL +#include "_usart_internal.h" + +/** Load from a binary buffer stored in Flash */ +void UUSART_loadBinary(Unit *unit, PayloadParser *pp) +{ + struct priv *priv = unit->data; + + uint8_t version = pp_u8(pp); + (void)version; + + priv->periph_num = pp_u8(pp); + priv->remap = pp_u8(pp); + + priv->baudrate = pp_u32(pp); + priv->parity = pp_u8(pp); + priv->stopbits = pp_u8(pp); + priv->direction = pp_u8(pp); + + priv->hw_flow_control = pp_u8(pp); + priv->clock_output = pp_bool(pp); + priv->cpol = pp_bool(pp); + priv->cpha = pp_bool(pp); + priv->lsb_first = pp_bool(pp); + priv->width = pp_u8(pp); + + priv->data_inv = pp_bool(pp); + priv->rx_inv = pp_bool(pp); + priv->tx_inv = pp_bool(pp); + + priv->de_output = pp_bool(pp); + priv->de_polarity = pp_bool(pp); + priv->de_assert_time = pp_u8(pp); + priv->de_clear_time = pp_u8(pp); +} + +/** Write to a binary buffer for storing in Flash */ +void UUSART_writeBinary(Unit *unit, PayloadBuilder *pb) +{ + struct priv *priv = unit->data; + + pb_u8(pb, 0); // version + + pb_u8(pb, priv->periph_num); + pb_u8(pb, priv->remap); + + pb_u32(pb, priv->baudrate); + pb_u8(pb, priv->parity); + pb_u8(pb, priv->stopbits); + pb_u8(pb, priv->direction); + + pb_u8(pb, priv->hw_flow_control); + pb_bool(pb, priv->clock_output); + pb_bool(pb, priv->cpol); + pb_bool(pb, priv->cpha); + pb_bool(pb, priv->lsb_first); + pb_u8(pb, priv->width); + + pb_bool(pb, priv->data_inv); + pb_bool(pb, priv->rx_inv); + pb_bool(pb, priv->tx_inv); + + pb_bool(pb, priv->de_output); + pb_bool(pb, priv->de_polarity); + pb_u8(pb, priv->de_assert_time); + pb_u8(pb, priv->de_clear_time); +} + +/** Parse a key-value pair from the INI file */ +error_t UUSART_loadIni(Unit *unit, const char *key, const char *value) +{ + bool suc = true; + struct priv *priv = unit->data; + + if (streq(key, "device")) { + priv->periph_num = cfg_u8_parse(value, &suc); + } + else if (streq(key, "remap")) { + priv->remap = cfg_u8_parse(value, &suc); + } + else if (streq(key, "baud-rate")) { + priv->baudrate = cfg_u32_parse(value, &suc); + } + else if (streq(key, "parity")) { + priv->parity = (uint8_t) cfg_enum3_parse(value, + "NONE", 0, + "ODD", 1, + "EVEN", 2, &suc); + } + else if (streq(key, "stop-bits")) { + priv->stopbits = (uint8_t) cfg_enum4_parse(value, + "0.5", 0, + "1", 1, + "1.5", 2, + "2", 3, &suc); + } + else if (streq(key, "direction")) { + priv->direction = (uint8_t) cfg_enum3_parse(value, + "RX", UUSART_DIRECTION_RX, + "TX", UUSART_DIRECTION_TX, + "RXTX", UUSART_DIRECTION_RXTX, &suc); + } + else if (streq(key, "hw-flow-control")) { + priv->hw_flow_control = (uint8_t) cfg_enum4_parse(value, + "NONE", 0, + "RTS", 1, + "CTS", 2, + "FULL", 3, &suc); + } + else if (streq(key, "word-width")) { + priv->width = cfg_u8_parse(value, &suc); + } + else if (streq(key, "first-bit")) { + priv->lsb_first = (bool) cfg_enum2_parse(value, "MSB", 0, "LSB", 1, &suc); + } + else if (streq(key, "clock-output")) { + priv->clock_output = cfg_bool_parse(value, &suc); + } + else if (streq(key, "cpol")) { + priv->cpol = cfg_bool_parse(value, &suc); + } + else if (streq(key, "cpha")) { + priv->cpha = cfg_bool_parse(value, &suc); + } + else if (streq(key, "de-output")) { + priv->de_output = cfg_bool_parse(value, &suc); + } + else if (streq(key, "de-polarity")) { + priv->de_polarity = cfg_bool_parse(value, &suc); + } + else if (streq(key, "de-assert-time")) { + priv->de_assert_time = cfg_u8_parse(value, &suc); + } + else if (streq(key, "de-clear-time")) { + priv->de_clear_time = cfg_u8_parse(value, &suc); + } + else { + return E_BAD_KEY; + } + + if (!suc) return E_BAD_VALUE; + return E_SUCCESS; +} + +/** Generate INI file section for the unit */ +void UUSART_writeIni(Unit *unit, IniWriter *iw) +{ + struct priv *priv = unit->data; + + iw_comment(iw, "Peripheral number (UARTx 1-4)"); + iw_entry_d(iw, "device", priv->periph_num); + + iw_comment(iw, "Pin mappings (TX,RX,CK,CTS,RTS/DE)"); +#if STM32F072xB + iw_comment(iw, " USART1: (0) A9,A10,A8,A11,A12 (1) B6,B7,A8,A11,A12"); + iw_comment(iw, " USART2: (0) A2,A3,A4,A0,A1 (1) A14,A15,A4,A0,A1"); + iw_comment(iw, " USART3: (0) B10,B11,B12,B13,B14"); + iw_comment(iw, " USART4: (0) A0,A1,C12,B7,A15 (1) C10,C11,C12,B7,A15"); +#elif GEX_PLAT_F103_BLUEPILL + #error "NO IMPL" +#elif GEX_PLAT_F303_DISCOVERY + #error "NO IMPL" +#elif GEX_PLAT_F407_DISCOVERY + #error "NO IMPL" +#else + #error "BAD PLATFORM!" +#endif + iw_entry_d(iw, "remap", priv->remap); + + iw_cmt_newline(iw); + iw_comment(iw, "Baud rate in bps (eg. 9600)"); + iw_entry_d(iw, "baud-rate", priv->baudrate); + + iw_comment(iw, "Parity type (NONE, ODD, EVEN)"); + iw_entry_s(iw, "parity", cfg_enum3_encode(priv->parity, + 0, "NONE", + 1, "ODD", + 2, "EVEN")); + + iw_comment(iw, "Number of stop bits (0.5, 1, 1.5, 2)"); + iw_entry_s(iw, "stop-bits", cfg_enum4_encode(priv->stopbits, + 0, "0.5", + 1, "1", + 2, "1.5", + 3, "2")); + + iw_comment(iw, "Bit order (LSB or MSB first)"); + iw_entry_s(iw, "first-bit", cfg_enum2_encode((uint32_t) priv->lsb_first, + 0, "MSB", + 1, "LSB")); + + iw_comment(iw, "Word width (7,8,9) - including parity bit if used"); + iw_entry_d(iw, "word-width", (int)priv->width); + + iw_comment(iw, "Enabled lines (RX,TX,RXTX)"); + iw_entry_s(iw, "direction", cfg_enum3_encode(priv->direction, + 1, "RX", + 2, "TX", + 3, "RXTX")); + + iw_comment(iw, "Hardware flow control (NONE, RTS, CTS, FULL)"); + iw_entry_s(iw, "hw-flow-control", cfg_enum4_encode(priv->hw_flow_control, + 0, "NONE", + 1, "RTS", + 2, "CTS", + 3, "FULL")); + + iw_cmt_newline(iw); + iw_comment(iw, "Generate serial clock (Y,N)"); + iw_entry_s(iw, "clock-output", str_yn(priv->clock_output)); + iw_comment(iw, "Clock polarity: 0,1"); + iw_entry_d(iw, "cpol", priv->cpol); + iw_comment(iw, "Clock phase: 0,1"); + iw_entry_d(iw, "cpha", priv->cpha); + + iw_cmt_newline(iw); + iw_comment(iw, "Generate RS485 Driver Enable signal (Y,N) - uses RTS pin"); + iw_entry_s(iw, "de-output", str_yn(priv->de_output)); + iw_comment(iw, "DE active level: 0,1"); + iw_entry_d(iw, "de-polarity", (priv->de_polarity)); + iw_comment(iw, "DE assert time (0-31)"); + iw_entry_d(iw, "de-assert-time", (priv->de_assert_time)); + iw_comment(iw, "DE clear time (0-31)"); + iw_entry_d(iw, "de-clear-time", (priv->de_clear_time)); +} diff --git a/GexUnits/usart/unit_usart.c b/GexUnits/usart/unit_usart.c new file mode 100644 index 0000000..3b3e829 --- /dev/null +++ b/GexUnits/usart/unit_usart.c @@ -0,0 +1,146 @@ +// +// Created by MightyPork on 2018/01/02. +// + +#include "unit_base.h" +#include "unit_usart.h" + +#define UUSART_INTERNAL +#include "_usart_internal.h" + +/** + * Data RX handler, run on the jobs thread. + * + * unit - unit + * timestamp - timestamp + * data1 - read start position + * data2 - nr of bytes to read + * + * @param job + */ +static void UUSART_SendReceivedDataToMaster(Job *job) +{ + Unit *unit = job->unit; + struct priv *priv = unit->data; + + uint16_t readpos = (uint16_t) job->data1; + uint16_t count = (uint16_t) job->data2; + + EventReport event = { + .unit = job->unit, + .timestamp = job->timestamp, + .data = (uint8_t *) (priv->rx_buffer + readpos), + .length = count, + }; + + EventReport_Send(&event); +} + +/** + * Handle received data (we're inside the IRQ). + * This is called either from a DMA complete / half interrupot, + * or form the timeout interrupt. + * + * @param unit - handled unit + * @param endpos - end position in the buffer + */ +void UUSART_DMA_HandleRxFromIRQ(Unit *unit, uint16_t endpos) +{ + const uint64_t ts = PTIM_GetMicrotime(); + + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + uint16_t readpos = priv->rx_buf_readpos; + assert_param(endpos > readpos); + + uint16_t count = (endpos - readpos); + + // We defer it to the job queue + Job j = { + .unit = unit, + .timestamp = ts, + .data1 = priv->rx_buf_readpos, + .data2 = count, + .cb = UUSART_SendReceivedDataToMaster + }; + scheduleJob(&j); // TODO disable unit on failure + + // Move the read cursor, wrap around if needed + if (endpos == UUSART_RXBUF_LEN) endpos = 0; + priv->rx_buf_readpos = endpos; +} + +/** + * Timed tick (ISR) - check timeout + */ +void UUSART_Tick(Unit *unit) +{ + assert_param(unit); + struct priv *priv = unit->data; + assert_param(priv); + + if (priv->rx_last_dmapos == priv->dma_rx->CNDTR) { + uint16_t endpos = (uint16_t) (UUSART_RXBUF_LEN - priv->rx_last_dmapos); + if (endpos != priv->rx_buf_readpos) { + UUSART_DMA_HandleRxFromIRQ(unit, endpos); + } + } else { + priv->rx_last_dmapos = (uint16_t) priv->dma_rx->CNDTR; + } +} + +enum PinCmd_ { + CMD_WRITE = 0, + CMD_WRITE_SYNC = 1, +}; + +/** Handle a request message */ +static error_t UUSART_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) +{ + uint32_t len; + const uint8_t *pld; + switch (command) { + /** + * Write bytes to the USART, without waiting for completion. + * May wait until there is space in the DMA buffer. + */ + case CMD_WRITE: + pld = pp_tail(pp, &len); + TRY(UU_USART_Write(unit, pld, len)); + return E_SUCCESS; + + /** + * Write bytes to the USART. Payload consists of the data to send. + * Waits for completion. + */ + case CMD_WRITE_SYNC: + pld = pp_tail(pp, &len); + TRY(UU_USART_WriteSync(unit, pld, len)); + return E_SUCCESS; + + default: + return E_UNKNOWN_COMMAND; + } +} + +// ------------------------------------------------------------------------ + +/** Unit template */ +const UnitDriver UNIT_USART = { + .name = "USART", + .description = "Serial port", + // Settings + .preInit = UUSART_preInit, + .cfgLoadBinary = UUSART_loadBinary, + .cfgWriteBinary = UUSART_writeBinary, + .cfgLoadIni = UUSART_loadIni, + .cfgWriteIni = UUSART_writeIni, + // Init + .init = UUSART_init, + .deInit = UUSART_deInit, + // Function + .updateTick = UUSART_Tick, + .handleRequest = UUSART_handleRequest, +}; diff --git a/GexUnits/usart/unit_usart.h b/GexUnits/usart/unit_usart.h new file mode 100644 index 0000000..158ba76 --- /dev/null +++ b/GexUnits/usart/unit_usart.h @@ -0,0 +1,39 @@ +// +// Created by MightyPork on 2018/01/02. +// +// USART driver using DMA buffers. +// Provides a wide range of config options and supports driving a RS485 driver. +// +// The implementation is complex and split to multiple files for easier maintenance +// + +#ifndef GEX_F072_UNIT_USART_H +#define GEX_F072_UNIT_USART_H + +#include "unit.h" + +extern const UnitDriver UNIT_USART; + +/** + * Write bytes. This function is asynchronous and does not wait for completion. + * It blocks until there's space in the Tx buffer for the data. + * + * @param unit + * @param buffer - bytes to send + * @param len - number of bytes to send + * @return success + */ +error_t UU_USART_Write(Unit *unit, const uint8_t *buffer, uint32_t len); + +/** + * Write bytes. Same like UU_USART_Write(), except it waits for the transmission + * to complete after sending the last data. + * + * @param unit + * @param buffer - bytes to send + * @param len - number of bytes to send + * @return success + */ +error_t UU_USART_WriteSync(Unit *unit, const uint8_t *buffer, uint32_t len); + +#endif //GEX_F072_UNIT_USART_H diff --git a/Makefile b/Makefile index c6be6f9..e963619 100644 --- a/Makefile +++ b/Makefile @@ -1,290 +1,105 @@ -########################################################################################################################## -# File automatically-generated by tool: [projectgenerator] version: [2.26.0] date: [Sat Dec 16 19:07:12 CET 2017] -########################################################################################################################## +################################################################### +# GEX F072 makefile +################################################################### -# ------------------------------------------------ -# Generic Makefile (based on gcc) -# -# ChangeLog : -# 2017-02-10 - Several enhancements + project update mode -# 2015-07-22 - first version -# ------------------------------------------------ -UNIT_NPX = 1 -UNIT_TEST = 1 -UNIT_DO = 1 -UNIT_DI = 1 -UNIT_USART = 1 -UNIT_1WIRE = 1 -UNIT_I2C = 1 -UNIT_SPI = 1 -UNIT_ADC = 1 -UNIT_SIPO = 1 -UNIT_FCAP = 1 -UNIT_TOUCH = 1 -UNIT_PWMDIM = 1 -UNIT_DAC = 1 +# Defaults - set in build.mk +GEX_PLAT = UNSET +DISABLE_DEBUG = 0 +DISABLE_MSC = 0 +CDC_LOOPBACK_TEST = 0 +DEBUG = 1 -DISABLE_DEBUG := 0 -DISABLE_MSC := 0 -CDC_LOOPBACK_TEST := 0 +PLAT_C_DIRS = +PLAT_C_FILES = +PLAT_C_INCLUDES = +PLAT_C_FLAGS = -include User/gex.mk -#GEX_PLAT=F072_ZERO -GEX_PLAT=F072_HUB -#GEX_PLAT=F072_DISCOVERY +PLAT_AS_DIRS = +PLAT_AS_FILES = +PLAT_AS_INCLUDES = +PLAT_AS_FLAGS = -###################################### -# target -###################################### -TARGET = firmware +# User params can be set here +include build.mk ###################################### -# building variables -###################################### -# debug build? -DEBUG = 1 -# optimization -OPT = -Os - - -####################################### -# paths -####################################### -#Application/User/Src/stm32f0xx_hal_timebase_TIM.c \ -# source path -SOURCES_DIR = \ -Application \ -Application/MAKEFILE \ -Application/User \ -Application/User/Src \ -Application/User/Src/freertos.c \ -Application/User/Src/gpio.c \ -Application/User/Src/main.c \ -Application/User/Src/stm32f0xx_hal_msp.c \ -Application/User/Src/stm32f0xx_it.c \ -Application/User/Src/usb.c \ -Drivers \ -Drivers/CMSIS \ -Drivers/STM32F0xx_HAL_Driver \ -Middlewares \ -Middlewares/FreeRTOS - -# firmware library path -PERIFLIB_PATH = -# Build path -BUILD_DIR = build +PLAT_C_DIRS += -###################################### -# source -###################################### # C sources -C_SOURCES = \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_cortex.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_dma.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_flash.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_flash_ex.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_i2c.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_i2c_ex.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pcd.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pcd_ex.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pwr.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pwr_ex.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_rcc.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_rcc_ex.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_tim.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_tim_ex.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_adc.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_comp.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_crc.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_crs.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_dac.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_dma.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_exti.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_gpio.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_i2c.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_pwr.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_rcc.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_rtc.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_spi.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_tim.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_usart.c \ -Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_utils.c \ -Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS/cmsis_os.c \ -Middlewares/Third_Party/FreeRTOS/Source/croutine.c \ -Middlewares/Third_Party/FreeRTOS/Source/event_groups.c \ -Middlewares/Third_Party/FreeRTOS/Source/list.c \ -Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM0/port.c \ -Middlewares/Third_Party/FreeRTOS/Source/queue.c \ -Middlewares/Third_Party/FreeRTOS/Source/tasks.c \ -Middlewares/Third_Party/FreeRTOS/Source/timers.c \ -Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c \ -Src/stm32f0xx_hal_msp.c \ -Src/system_stm32f0xx.c \ -Src/main.c -#Src/stm32f0xx_hal_timebase_TIM.c \ -#Src/stm32f0xx_it.c \ - -C_SOURCES += $(foreach x,$(GEX_SRC_DIR),$(wildcard $(x)/*.c)) $(GEX_SOURCES) - -# ASM sources -ASM_SOURCES = \ -startup_stm32f072xb.s - - -###################################### -# firmware library -###################################### -PERIFLIB_SOURCES = - - -####################################### -# binaries -####################################### -BINPATH = /usr/bin -PREFIX = arm-none-eabi- -CC = $(BINPATH)/$(PREFIX)gcc -AS = $(BINPATH)/$(PREFIX)gcc -x assembler-with-cpp -CP = $(BINPATH)/$(PREFIX)objcopy -AR = $(BINPATH)/$(PREFIX)ar -SZ = $(BINPATH)/$(PREFIX)size -HEX = $(CP) -O ihex -BIN = $(CP) -O binary -S - -####################################### -# CFLAGS -####################################### -# cpu -CPU = -mcpu=cortex-m0plus - -# fpu -# NONE for Cortex-M0/M0+/M3 - -# float-abi - - -# mcu -MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI) - -# macros for gcc -# AS defines -AS_DEFS = - -# C defines -C_DEFS = \ --DUSE_HAL_DRIVER \ --DSTM32F072xB \ --DARM_MATH_CM0PLUS - -C_DEFS += -DGEX_PLAT_$(GEX_PLAT) $(GEX_CDEFS) +PLAT_C_FILES += \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_cortex.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_dma.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_flash.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_flash_ex.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_i2c.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_i2c_ex.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pcd.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pcd_ex.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pwr.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pwr_ex.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_rcc.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_rcc_ex.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_tim.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_tim_ex.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_adc.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_comp.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_crc.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_crs.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_dac.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_dma.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_exti.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_gpio.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_i2c.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_pwr.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_rcc.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_rtc.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_spi.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_tim.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_usart.c \ + Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_ll_utils.c \ + Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM0/port.c \ + Src/stm32f0xx_hal_msp.c \ + Src/system_stm32f0xx.c + +PLAT_C_INCLUDES += \ + -IDrivers/STM32F0xx_HAL_Driver/Inc \ + -IDrivers/STM32F0xx_HAL_Driver/Inc/Legacy \ + -IMiddlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM0 \ + -IDrivers/CMSIS/Device/ST/STM32F0xx/Include + +PLAT_C_FLAGS += \ + -DSTM32F072xB \ + -DARM_MATH_CM0PLUS + +############################################## + +PLAT_AS_DIRS += + +PLAT_AS_FILES += \ + startup_stm32f072xb.s + +PLAT_AS_INCLUDES += + +PLAT_AS_FLAGS += + +############################################## + +# CPU flags +PLAT_CPU = -mcpu=cortex-m0plus + +# FPU flags +PLAT_FLOAT-ABI = -# AS includes -AS_INCLUDES = \ --IInc - -# C includes -C_INCLUDES = \ --IInc \ --IDrivers/STM32F0xx_HAL_Driver/Inc \ --IDrivers/STM32F0xx_HAL_Driver/Inc/Legacy \ --IMiddlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM0 \ --IDrivers/CMSIS/Device/ST/STM32F0xx/Include \ --IMiddlewares/Third_Party/FreeRTOS/Source/include \ --IMiddlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS \ --IDrivers/CMSIS/Include - -C_INCLUDES += $(GEX_INCLUDES) - -# compile gcc flags -ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections - -CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections $(GEX_CFLAGS) - -ifeq ($(DEBUG), 1) -CFLAGS += -ggdb -g -gdwarf-2 -endif - - -# Generate dependency information -CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" - - -####################################### -# LDFLAGS -####################################### # link script -LDSCRIPT = STM32F072RBTx_FLASH.ld - -# libraries -LIBS = -lc -lm -lnosys -LIBDIR = -LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections - -# default action: build all -all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin - - -####################################### -# build the application -####################################### -# list of objects -OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o))) -vpath %.c $(sort $(dir $(C_SOURCES))) -# list of ASM program objects -OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o))) -vpath %.s $(sort $(dir $(ASM_SOURCES))) - -$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) - @printf "CC $<\n" - @$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@ - -$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR) - @$(AS) -c $(CFLAGS) $< -o $@ - -$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile - @$(CC) $(OBJECTS) $(LDFLAGS) -o $@ - @$(SZ) $@ - -$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR) - @$(HEX) $< $@ - -$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR) - @$(BIN) $< $@ - -flash: $(BUILD_DIR)/$(TARGET).bin - @printf " FLASH $<\n" - @st-flash write $< 0x8000000 - -$(BUILD_DIR)/$(TARGET).dfu: $(BUILD_DIR)/$(TARGET).hex - @printf " DFU-GEN $<\n" - dfu-convert -i $< $@ - -dfu: $(BUILD_DIR)/$(TARGET).dfu - @printf " DFU UPLOAD $<\n" - dfu-util -a 0 -D $< - -patch: - @./cubepatch.sh - -dis: $(BUILD_DIR)/$(TARGET).elf - arm-none-eabi-objdump -Sslrtd build/$(TARGET).elf > disassembly.lst +PLAT_LDSCRIPT = STM32F072RBTx_FLASH.ld -$(BUILD_DIR): - @mkdir -p $@ +############################################## -####################################### -# clean up -####################################### -clean: - -rm -fR .dep $(BUILD_DIR) - -####################################### -# dependencies -####################################### --include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*) +include GexCore/gex.mk # *** EOF *** diff --git a/User b/User deleted file mode 160000 index 482a67c..0000000 --- a/User +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 482a67c5d287d4946ed9ff2d50158d4e7fda7096 diff --git a/build.mk b/build.mk new file mode 100644 index 0000000..3aa26a2 --- /dev/null +++ b/build.mk @@ -0,0 +1,25 @@ + +GEX_PLAT=F072_ZERO +#GEX_PLAT=F072_HUB +#GEX_PLAT=F072_DISCOVERY + +DISABLE_DEBUG = 0 +DISABLE_MSC = 0 +CDC_LOOPBACK_TEST = 0 + +# Units included in the built image +GEX_UNITS += 1wire +GEX_UNITS += adc +GEX_UNITS += dac +GEX_UNITS += din +GEX_UNITS += dout +GEX_UNITS += fcap +GEX_UNITS += i2c +GEX_UNITS += neopixel +GEX_UNITS += pwmdim +GEX_UNITS += sipo +GEX_UNITS += spi +#GEX_UNITS += template +#GEX_UNITS += test +GEX_UNITS += touch +GEX_UNITS += usart