Modularization, stage 1

master
Ondřej Hruška 6 years ago
parent e5679e90f8
commit a1fa0821cf
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 3
      .gitmodules
  2. 129
      GexUnits/1wire/_ow_api.c
  3. 18
      GexUnits/1wire/_ow_commands.h
  4. 59
      GexUnits/1wire/_ow_init.c
  5. 60
      GexUnits/1wire/_ow_internal.h
  6. 223
      GexUnits/1wire/_ow_low_level.c
  7. 84
      GexUnits/1wire/_ow_low_level.h
  8. 129
      GexUnits/1wire/_ow_search.c
  9. 83
      GexUnits/1wire/_ow_search.h
  10. 68
      GexUnits/1wire/_ow_settings.c
  11. 246
      GexUnits/1wire/unit_1wire.c
  12. 79
      GexUnits/1wire/unit_1wire.h
  13. 700
      GexUnits/adc/_adc_core.c
  14. 269
      GexUnits/adc/_adc_init.c
  15. 161
      GexUnits/adc/_adc_internal.h
  16. 117
      GexUnits/adc/_adc_settings.c
  17. 428
      GexUnits/adc/unit_adc.c
  18. 15
      GexUnits/adc/unit_adc.h
  19. 84
      GexUnits/dac/_dac_api.c
  20. 392
      GexUnits/dac/_dac_core.c
  21. 144
      GexUnits/dac/_dac_init.c
  22. 111
      GexUnits/dac/_dac_internal.h
  23. 131
      GexUnits/dac/_dac_settings.c
  24. 166
      GexUnits/dac/unit_dac.c
  25. 16
      GexUnits/dac/unit_dac.h
  26. 71
      GexUnits/din/_din_api.c
  27. 80
      GexUnits/din/_din_exti.c
  28. 139
      GexUnits/din/_din_init.c
  29. 65
      GexUnits/din/_din_internal.h
  30. 118
      GexUnits/din/_din_settings.c
  31. 94
      GexUnits/din/unit_din.c
  32. 42
      GexUnits/din/unit_din.h
  33. 173
      GexUnits/dout/_dout_api.c
  34. 71
      GexUnits/dout/_dout_init.c
  35. 54
      GexUnits/dout/_dout_internal.h
  36. 82
      GexUnits/dout/_dout_settings.c
  37. 66
      GexUnits/dout/unit_dout.c
  38. 71
      GexUnits/dout/unit_dout.h
  39. 11
      GexUnits/fcap/_fcap_api.c
  40. 461
      GexUnits/fcap/_fcap_core.c
  41. 147
      GexUnits/fcap/_fcap_init.c
  42. 117
      GexUnits/fcap/_fcap_internal.h
  43. 114
      GexUnits/fcap/_fcap_settings.c
  44. 322
      GexUnits/fcap/unit_fcap.c
  45. 16
      GexUnits/fcap/unit_fcap.h
  46. 123
      GexUnits/i2c/_i2c_api.c
  47. 160
      GexUnits/i2c/_i2c_init.c
  48. 51
      GexUnits/i2c/_i2c_internal.h
  49. 103
      GexUnits/i2c/_i2c_settings.c
  50. 88
      GexUnits/i2c/unit_i2c.c
  51. 76
      GexUnits/i2c/unit_i2c.h
  52. 53
      GexUnits/neopixel/_npx_api.c
  53. 58
      GexUnits/neopixel/_npx_init.c
  54. 52
      GexUnits/neopixel/_npx_internal.h
  55. 61
      GexUnits/neopixel/_npx_settings.c
  56. 76
      GexUnits/neopixel/unit_neopixel.c
  57. 53
      GexUnits/neopixel/unit_neopixel.h
  58. 83
      GexUnits/neopixel/ws2812.c
  59. 37
      GexUnits/neopixel/ws2812.h
  60. 64
      GexUnits/pwmdim/_pwmdim_api.c
  61. 170
      GexUnits/pwmdim/_pwmdim_init.c
  62. 65
      GexUnits/pwmdim/_pwmdim_internal.h
  63. 89
      GexUnits/pwmdim/_pwmdim_settings.c
  64. 69
      GexUnits/pwmdim/unit_pwmdim.c
  65. 16
      GexUnits/pwmdim/unit_pwmdim.h
  66. 110
      GexUnits/sipo/_sipo_api.c
  67. 113
      GexUnits/sipo/_sipo_init.c
  68. 120
      GexUnits/sipo/_sipo_internal.h
  69. 116
      GexUnits/sipo/_sipo_settings.c
  70. 73
      GexUnits/sipo/unit_sipo.c
  71. 16
      GexUnits/sipo/unit_sipo.h
  72. 103
      GexUnits/spi/_spi_api.c
  73. 195
      GexUnits/spi/_spi_init.c
  74. 59
      GexUnits/spi/_spi_internal.h
  75. 141
      GexUnits/spi/_spi_settings.c
  76. 84
      GexUnits/spi/unit_spi.c
  77. 63
      GexUnits/spi/unit_spi.h
  78. 2
      GexUnits/template/!README.TXT
  79. 11
      GexUnits/template/_tpl_api.c
  80. 49
      GexUnits/template/_tpl_init.c
  81. 46
      GexUnits/template/_tpl_internal.h
  82. 58
      GexUnits/template/_tpl_settings.c
  83. 23
      GexUnits/template/install.sh
  84. 57
      GexUnits/template/unit_tpl.c
  85. 16
      GexUnits/template/unit_tpl.h
  86. 177
      GexUnits/test/unit_test.c
  87. 14
      GexUnits/test/unit_test.h
  88. 11
      GexUnits/touch/_touch_api.c
  89. 217
      GexUnits/touch/_touch_core.c
  90. 221
      GexUnits/touch/_touch_init.c
  91. 95
      GexUnits/touch/_touch_internal.h
  92. 187
      GexUnits/touch/_touch_settings.c
  93. 136
      GexUnits/touch/unit_touch.c
  94. 16
      GexUnits/touch/unit_touch.h
  95. 65
      GexUnits/units_manifest.h
  96. 56
      GexUnits/usart/_usart_api.c
  97. 444
      GexUnits/usart/_usart_dmas.c
  98. 344
      GexUnits/usart/_usart_init.c
  99. 118
      GexUnits/usart/_usart_internal.h
  100. 233
      GexUnits/usart/_usart_settings.c
  101. Some files were not shown because too many files have changed in this diff Show More

3
.gitmodules vendored

@ -1,3 +0,0 @@
[submodule "User"]
path = User
url = https://git.ondrovo.com/gex/gex-core.git

@ -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;
}

@ -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

@ -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);
}

@ -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

@ -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;
}

@ -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

@ -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;
}

@ -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 <stdint.h>
#include <stdbool.h>
#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

@ -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));
}

@ -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,
};

@ -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

@ -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;
}

@ -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);
}

@ -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

@ -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);
}

@ -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<<i)) {
priv->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,
};

@ -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

@ -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);
}
}

@ -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

@ -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);
}

@ -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

@ -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);
}

@ -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<<i)) {
switch (command) {
case CMD_SET_FREQUENCY:;
float freq = pp_float(pp);
TRY(UDAC_SetFreq(unit, i, freq));
break;
case CMD_WAVE_DC:
priv->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,
};

@ -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

@ -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;
}

@ -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);
}
}

@ -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);
}

@ -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

@ -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
}

@ -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,
};

@ -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

@ -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<<i)) {
odr |= (1<<i);
} else if (priv->msec_pulse_scheduled_0 & (1<<i)) {
odr &= ~(1<<i);
}
if (priv->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;
}
}

@ -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);
}

@ -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

@ -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));
}

@ -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,
};

@ -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

@ -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"

@ -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);
}

@ -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);
}

@ -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

@ -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"));
}

@ -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,
};

@ -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

@ -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, &regnum, 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;
}

@ -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);
}

@ -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

@ -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);
}

@ -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,
};

@ -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

@ -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;
}

@ -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);
}

@ -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

@ -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);
}

@ -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,
};

@ -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

@ -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);
}

@ -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

@ -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;
}

@ -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);
}

@ -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

@ -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);
}

@ -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,
};

@ -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

@ -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;
}

@ -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);
}

@ -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

@ -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));
}

@ -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,
};

@ -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

@ -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;
}

@ -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<<SPI_CR1_BR_Pos)&SPI_CR1_BR_Msk);
LL_SPI_SetClockPolarity(priv->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);
}

@ -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

@ -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));
}

@ -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,
};

@ -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

@ -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.

@ -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"

@ -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);
}

@ -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

@ -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;
//
}

@ -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

@ -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,
};

@ -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

@ -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,
};

@ -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

@ -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"

@ -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<<i)) {
priv->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<<i)) {
PRINTF("%d ", (int)priv->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<<priv->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;
}

@ -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);
}

@ -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

@ -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));
}
}

@ -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<<i)) {
pb_u16(&pb, priv->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<<i)) {
priv->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<<i;
}
}
}
return E_SUCCESS;
/**
* Set the debounce time in ms (replaces the default value from settings)
*
* pld: ms:u16
*/
case CMD_SET_DEBOUNCE_TIME:
priv->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<<i)) {
priv->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<<i)) {
nb++;
}
}
pb_u8(&pb, nb);
com_respond_pb(frame_id, MSG_SUCCESS, &pb);
return E_SUCCESS;
default:
return E_UNKNOWN_COMMAND;
}
}
// ------------------------------------------------------------------------
/** Unit template */
const UnitDriver UNIT_TOUCH = {
.name = "TOUCH",
.description = "Capacitive touch sensing",
// Settings
.preInit = UTOUCH_preInit,
.cfgLoadBinary = UTOUCH_loadBinary,
.cfgWriteBinary = UTOUCH_writeBinary,
.cfgLoadIni = UTOUCH_loadIni,
.cfgWriteIni = UTOUCH_writeIni,
// Init
.init = UTOUCH_init,
.deInit = UTOUCH_deInit,
// Function
.handleRequest = UTOUCH_handleRequest,
.updateTick = UTOUCH_updateTick,
};

@ -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_TOUCH_H
#define U_TOUCH_H
#include "unit.h"
extern const UnitDriver UNIT_TOUCH;
// UU_ prototypes
#endif //U_TOUCH_H

@ -0,0 +1,65 @@
// This is a GEX units manifest file
// For a unit to be included in the firmware, it's uppercase name
// must be included in the GEX_UNITS list in build.mk, and it must have
// a section here.
#ifdef ENABLE_UNIT_1WIRE
#include "1wire/unit_1wire.h"
#endif
#ifdef ENABLE_UNIT_ADC
#include "adc/unit_adc.h"
#endif
#ifdef ENABLE_UNIT_DAC
#include "dac/unit_dac.h"
#endif
#ifdef ENABLE_UNIT_DIN
#include "din/unit_din.h"
#endif
#ifdef ENABLE_UNIT_DOUT
#include "dout/unit_dout.h"
#endif
#ifdef ENABLE_UNIT_FCAP
#include "fcap/unit_fcap.h"
#endif
#ifdef ENABLE_UNIT_I2C
#include "i2c/unit_i2c.h"
#endif
#ifdef ENABLE_UNIT_NEOPIXEL
#include "neopixel/unit_neopixel.h"
#endif
#ifdef ENABLE_UNIT_PWMDIM
#include "pwmdim/unit_pwmdim.h"
#endif
#ifdef ENABLE_UNIT_SIPO
#include "sipo/unit_sipo.h"
#endif
#ifdef ENABLE_UNIT_SPI
#include "spi/unit_spi.h"
#endif
#ifdef ENABLE_UNIT_TEMPLATE
#include "template/unit_template.h"
#endif
#ifdef ENABLE_UNIT_TEST
#include "test/unit_test.h"
#endif
#ifdef ENABLE_UNIT_TOUCH
#include "touch/unit_touch.h"
#endif
#ifdef ENABLE_UNIT_USART
#include "usart/unit_usart.h"
#endif

@ -0,0 +1,56 @@
//
// Created by MightyPork on 2018/02/03.
//
#include "platform.h"
#include "unit_base.h"
#include "unit_usart.h"
#define UUSART_INTERNAL
#include "_usart_internal.h"
error_t UU_USART_Write(Unit *unit, const uint8_t *buffer, uint32_t len)
{
CHECK_TYPE(unit, &UNIT_USART);
struct priv *priv = unit->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;
}

@ -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);
}

@ -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;
}

@ -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

@ -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));
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save