Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Ondřej Hruška | 4a041485fb | 7 years ago |
Ondřej Hruška | af70cd7488 | 7 years ago |
@ -1,3 +1,3 @@ |
||||
[submodule "GexCore"] |
||||
path = GexCore |
||||
url = https://git.ondrovo.com/gex/gex-core.git |
||||
[submodule "User"] |
||||
path = User |
||||
url = git@github.com:MightyPork/gex-core |
||||
|
@ -1 +0,0 @@ |
||||
Subproject commit 3d3d74f3817e89a8c3ce2cb36501ad292b6ddc85 |
@ -1,129 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,18 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,59 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,60 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,223 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,84 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,129 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
||||
|
||||
|
@ -1,83 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,68 +0,0 @@ |
||||
//
|
||||
// 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)); |
||||
} |
@ -1,246 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,79 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,700 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,269 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,161 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,117 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
||||
|
@ -1,428 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,15 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,84 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
||||
} |
@ -1,392 +0,0 @@ |
||||
//
|
||||
// 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 |
@ -1,144 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,111 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,131 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,166 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,16 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,71 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,80 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
||||
} |
@ -1,139 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,65 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,118 +0,0 @@ |
||||
//
|
||||
// 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 |
||||
} |
||||
|
@ -1,94 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,42 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,173 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
||||
} |
@ -1,71 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,54 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,82 +0,0 @@ |
||||
//
|
||||
// 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)); |
||||
} |
@ -1,66 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,71 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,11 +0,0 @@ |
||||
//
|
||||
// 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" |
||||
|
@ -1,461 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,147 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,117 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,114 +0,0 @@ |
||||
//
|
||||
// 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")); |
||||
} |
||||
|
@ -1,322 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,16 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,123 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#include "platform.h" |
||||
#include "unit_base.h" |
||||
#include "unit_i2c.h" |
||||
|
||||
#define I2C_INTERNAL |
||||
#include "_i2c_internal.h" |
||||
|
||||
static void i2c_reset(struct priv *priv) |
||||
{ |
||||
LL_I2C_Disable(priv->periph); |
||||
HAL_Delay(1); |
||||
LL_I2C_Enable(priv->periph); |
||||
} |
||||
|
||||
static error_t i2c_wait_until_flag(struct priv *priv, uint32_t flag, bool stop_state) |
||||
{ |
||||
uint32_t t_start = HAL_GetTick(); |
||||
while (((priv->periph->ISR & flag)!=0) != stop_state) { |
||||
if (HAL_GetTick() - t_start > 10) { |
||||
i2c_reset(priv); |
||||
return E_HW_TIMEOUT; |
||||
} |
||||
} |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_Write(Unit *unit, uint16_t addr, const uint8_t *bytes, uint32_t bcount) |
||||
{ |
||||
CHECK_TYPE(unit, &UNIT_I2C); |
||||
|
||||
struct priv *priv = unit->data; |
||||
|
||||
uint8_t addrsize = (uint8_t) (((addr & 0x8000) == 0) ? 7 : 10); |
||||
addr &= 0x3FF; |
||||
uint32_t ll_addrsize = (addrsize == 7) ? LL_I2C_ADDRSLAVE_7BIT : LL_I2C_ADDRSLAVE_10BIT; |
||||
if (addrsize == 7) addr <<= 1; // 7-bit address must be shifted to left for LL to use it correctly
|
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_BUSY, 0)); |
||||
|
||||
bool first = true; |
||||
while (bcount > 0) { |
||||
uint32_t len = bcount; |
||||
uint32_t chunk_remain = (uint8_t) ((len > 255) ? 255 : len); // if more than 255, first chunk is 255
|
||||
LL_I2C_HandleTransfer(priv->periph, addr, ll_addrsize, chunk_remain, |
||||
(len > 255) ? LL_I2C_MODE_RELOAD : LL_I2C_MODE_AUTOEND, // Autoend if this is the last chunk
|
||||
first ? LL_I2C_GENERATE_START_WRITE : LL_I2C_GENERATE_NOSTARTSTOP); // no start/stop condition if we're continuing
|
||||
first = false; |
||||
bcount -= chunk_remain; |
||||
|
||||
for (; chunk_remain > 0; chunk_remain--) { |
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_TXIS, 1)); |
||||
uint8_t byte = *bytes++; |
||||
LL_I2C_TransmitData8(priv->periph, byte); |
||||
} |
||||
} |
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_STOPF, 1)); |
||||
LL_I2C_ClearFlag_STOP(priv->periph); |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_Read(Unit *unit, uint16_t addr, uint8_t *dest, uint32_t bcount) |
||||
{ |
||||
CHECK_TYPE(unit, &UNIT_I2C); |
||||
|
||||
struct priv *priv = unit->data; |
||||
|
||||
uint8_t addrsize = (uint8_t) (((addr & 0x8000) == 0) ? 7 : 10); |
||||
addr &= 0x3FF; |
||||
uint32_t ll_addrsize = (addrsize == 7) ? LL_I2C_ADDRSLAVE_7BIT : LL_I2C_ADDRSLAVE_10BIT; |
||||
if (addrsize == 7) addr <<= 1; // 7-bit address must be shifted to left for LL to use it correctly
|
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_BUSY, 0)); |
||||
|
||||
bool first = true; |
||||
while (bcount > 0) { |
||||
if (!first) { |
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_TCR, 1)); |
||||
} |
||||
|
||||
uint8_t chunk_remain = (uint8_t) ((bcount > 255) ? 255 : bcount); // if more than 255, first chunk is 255
|
||||
LL_I2C_HandleTransfer(priv->periph, addr, ll_addrsize, chunk_remain, |
||||
(bcount > 255) ? LL_I2C_MODE_RELOAD : LL_I2C_MODE_AUTOEND, // Autoend if this is the last chunk
|
||||
first ? LL_I2C_GENERATE_START_READ : LL_I2C_GENERATE_NOSTARTSTOP); // no start/stop condition if we're continuing
|
||||
first = false; |
||||
bcount -= chunk_remain; |
||||
|
||||
for (; chunk_remain > 0; chunk_remain--) { |
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_RXNE, 1)); |
||||
|
||||
uint8_t byte = LL_I2C_ReceiveData8(priv->periph); |
||||
*dest++ = byte; |
||||
} |
||||
} |
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_STOPF, 1)); |
||||
LL_I2C_ClearFlag_STOP(priv->periph); |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_ReadReg(Unit *unit, uint16_t addr, uint8_t regnum, uint8_t *dest, uint32_t width) |
||||
{ |
||||
TRY(UU_I2C_Write(unit, addr, ®num, 1)); |
||||
TRY(UU_I2C_Read(unit, addr, dest, width)); |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_WriteReg(Unit *unit, uint16_t addr, uint8_t regnum, const uint8_t *bytes, uint32_t width) |
||||
{ |
||||
CHECK_TYPE(unit, &UNIT_I2C); |
||||
|
||||
// we have to insert the address first - needs a buffer (XXX realistically the buffer needs 1-4 bytes + addr)
|
||||
PayloadBuilder pb = pb_start((uint8_t*)unit_tmp512, UNIT_TMP_LEN, NULL); |
||||
pb_u8(&pb, regnum); |
||||
pb_buf(&pb, bytes, width); |
||||
|
||||
TRY(UU_I2C_Write(unit, addr, (uint8_t *) unit_tmp512, pb_length(&pb))); |
||||
return E_SUCCESS; |
||||
} |
@ -1,160 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,51 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,103 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,88 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,76 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,53 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,58 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,52 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,61 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,76 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,53 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,83 +0,0 @@ |
||||
#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); |
||||
} |
@ -1,37 +0,0 @@ |
||||
#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
|
@ -1,64 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,170 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,65 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,89 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
||||
|
@ -1,69 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,16 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,110 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,113 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,120 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,116 +0,0 @@ |
||||
//
|
||||
// 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)); |
||||
} |
||||
|
@ -1,73 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,16 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,103 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,195 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,59 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,141 +0,0 @@ |
||||
//
|
||||
// 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)); |
||||
} |
@ -1,84 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,63 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,2 +0,0 @@ |
||||
This is a template unit, used for reference when creating new units. |
||||
It is not registered into the unit registry, and cannot be instantiated. |
@ -1,11 +0,0 @@ |
||||
//
|
||||
// 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" |
||||
|
@ -1,49 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,46 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,58 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
|
||||
//
|
||||
} |
||||
|
@ -1,23 +0,0 @@ |
||||
#!/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 |
@ -1,57 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,16 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,11 +0,0 @@ |
||||
//
|
||||
// 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" |
||||
|
@ -1,217 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,221 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,95 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,187 +0,0 @@ |
||||
//
|
||||
// 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)); |
||||
} |
||||
} |
||||
|
@ -1,136 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,16 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,57 +0,0 @@ |
||||
// This is a GEX units manifest file
|
||||
// For a unit to be included in the firmware, its uppercase name
|
||||
// must be included in the GEX_UNITS list in build.mk, and it must have
|
||||
// an entry 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_TOUCH |
||||
#include "touch/unit_touch.h" |
||||
#endif |
||||
|
||||
#ifdef ENABLE_UNIT_USART |
||||
#include "usart/unit_usart.h" |
||||
#endif |
||||
|
@ -1,56 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,444 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,344 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue