Compare commits

...

10 Commits

Author SHA1 Message Date
true 6a2e8164d9 Fix flash routine bugs, make it work 2023-11-01 10:15:18 -07:00
true b3c799d7e7 Fixed config flash routines, added probe calibration routine
Have only tested one config write so far. Will test others and fix if needed
2023-11-01 09:43:14 -07:00
true 6394c3aaff Implement flash config storage
Calibration needs to be saved for each badge in continuity mode. Added a flash-backed user config storing option with basic wear leveling. Have NOT yet tested the write and erase functions.
2023-11-01 07:45:44 -07:00
true ad383a76ac Changed error flash routine
Need to add other features that involve flashing the error code lights, so made it possible to do this with different colors and without interfering with running programs
2023-11-01 06:05:40 -07:00
true 418fdafe56 Change some ADC parameters
Still getting ~64 reads per update cycle, but sampling a little slower. After doing some testing it may - or may not - result in more stability of readings.

Doubled the count of averaged readings.
2023-11-01 06:05:01 -07:00
true 36a55a865b Fix diode check limits, allow for two-mode continuity limit
D1 has changed to a 5V1 zener and is loading the circuit a little bit. Adjusted voltage based on tested value on prototype. This value may differ on production units so expect this to possibly change.
2023-10-31 19:50:30 -07:00
true f2d4afce1c Add error mode indicator when SET1 is set above max 2023-10-31 08:22:27 -07:00
true cfd9715023 Update README.md 2023-10-31 08:22:07 -07:00
true f504e921d6 Create README.md 2023-10-31 08:02:09 -07:00
true 7fb2dee523 platformio.ini and linker script fixups 2023-10-31 07:46:25 -07:00
14 changed files with 641 additions and 89 deletions

86
README.md Normal file
View File

@ -0,0 +1,86 @@
## TESO-O Fun Robot at Hackaday Supercon 2023
Firmware for TEST-O: The Continuity and Diode Test Funbot
This is a PlatformIO-compatible project.
 
**BE AWARE:**
On REV1 boards, the right potentiometer only works for the first half of its rotation. Turning the right pot beyond half way _will not cause damage_, but the addon will consume extra current (up to ~3-5mA). If the knob is within the affected second half of rotation, the eye LEDs will flash red as an indicator while in RGB fun mode.
To work around this, use the right pot only within the first half of its rotation.
 
## What can you do with it?
The TEST-O Robot is:
- A continuity meter
- Set the switch to the left position.
- Use the provided probes to check continuity.
- The left eye will show continuity status.
- Continuity is indicated if a resistance of ~100ohms or less is detected.
- Turn the buzzer on or off using the button while in this mode.
- Testing voltage is ~1.2V and limited to ~2mA.
- A diode test meter
- Set the switch to the right position.
- Use the provided probes on the diode.
- The right eye will show diode test status.
- Status is updated if the diode conducts (positive on anode, negative on cathode).
- Shorts are shown with a different eye LED color.
- LEDs can be tested, though not all will light or indicate on the right eye.
- Testing voltage is ~3.1V and limited to ~2mA.
- An RGB light show with long dangly probe arms
- Up to you if you want to short things out with dangly probes
- 7 configurable RGB programs available
- Turn the potentiometers to set program parameters
- There is an empty RGB LED program slot available for hackers to add one
## Hacking
Use a DAPLink probe to debug. If you don't have one, there are many projects to convert existing devboards to DAPLink.
If you want to hack on this without a debug probe, an XMODEM bootloader is available. Use a TTL UART to connect to the UART pins on the addon header. To activate the bootloader, ensure the left knob is between halfway to full clockwise. Press and hold the button while powering on. The bootloader will now indicate it is working with flashing LEDs, and will communicate at 115200-8-N-1. Send your firmware.bin file using XMODEM using a terminal emulator.
## Specifications
- PUYA PY32F003W16S6 MCU
- ARM Cortex-M0+ core running at 8MHz
- 32K Flash / 4K RAM
- 2x RGBLED for indicators or fun mode
- Low-power piezo buzzer for continuity test
- Built-in continuity cheeks and LED test antenna
- Pots to set things just how you want them
- Works as an addon, or standalone with a CR2032
## Misc Notes:
- This device is using PUYA PY32F003 MCU, an MCU intended for
the China domestic market. This MCU, as well as other
PY32F002A/003/030 cores with at least 32K flash, internally
uses the same core (and has the same features) as PY32F030F6P6.
- The normal firwmare does not use UART and leaves the pins configured
as SWD for the user.
- There is no official OpenOCD support for this MCU. This project uses
pyOCD as adding PUYA MCU to pyOCD is trivial. Using OpenOCD, J-Link,
or anything other than the DAPLink compatible debugger with pyOCD
is untested and left as an exercise to the programmer.
## Credits
Original idea by rCON. Idea tweaked and implemented by true.
Art modified by rCON and further modified by true.
Schematic, board layout, code, and assembly by true.

View File

@ -14,11 +14,11 @@
#define ADC_CHANNELS 5
#define ADC_HISTLEN 16
#define ADC_HISTLEN 32
extern uint16_t adc_avg[ADC_CHANNELS];
extern uint32_t adc_avg[ADC_CHANNELS];

View File

@ -16,9 +16,39 @@
#define FLASH_HSI_24MHz (uint32_t *)0x1FFF0F6C
#define UCONF_FLASH_SIZE 512
#define UCONF_FLASH_START (FLASH_END + 1) - UCONF_FLASH_SIZE
#define UCONF_SIZE 64 // size in bytes
#define UCONF_COUNT (UCONF_FLASH_SIZE / UCONF_SIZE)
#define UCONF_KEY 0x3713
typedef struct UConf {
uint16_t conf_key; // should read 0x3713
uint8_t cont_buzzer; // continuity buzzer on/off
uint8_t cont_sensitivity; // continuity sensitivity high/low
uint16_t cont_shorted; // ADC counts when probes shorted in continuity mode
uint16_t diode_shorted; // unused, unneeded
uint32_t rsvd_1[4];
uint32_t rsvd_2[8];
uint32_t update_cntr; // increment every update of config
uint32_t crc32; // CRC of preceding 60 bytes
} UConf; // 64 bytes
extern struct UConf conf;
void flash_init();
void flash_load_conf();
void flash_commit_conf();
#endif /* _INC_FLASH_H */

View File

@ -8,9 +8,14 @@
#define _INC_RGBPROG_H
#include <stdint.h>
void rgbprog_run();
void rgbprog_error_flasher(uint8_t scale, uint8_t r, uint8_t g, uint8_t b);
#endif /* _INC_RGBPROG_H */

View File

@ -32,6 +32,11 @@ void userio_parse();
uint8_t userio_get_mode();
int16_t userio_get_btn();
uint8_t userio_get_set1_limit();
int16_t userio_get_btn_held();
void userio_set_btn_override();
#endif /* _INC_USERIO_H */

View File

@ -15,6 +15,7 @@ platform = nxplpc ; not actually this and we are not using
board = generic_py32f030x6
board_build.ldscript = py32f030x6.ld
debug_tool = custom ; built-in pyocd support does not allow config file override
debug_server = $PYTHONEXE
${platformio.packages_dir}/tool-pyocd/pyocd-gdbserver.py
@ -22,7 +23,6 @@ debug_server = $PYTHONEXE
$PROJECT_DIR/pyocd.yaml
-t
PY32F030x6
#debug_port = localhost:3333
debug_extra_cmds =
set mem inaccessible-by-default off
@ -56,14 +56,14 @@ debug_build_flags =
[env:sc7-testobot_REV1-py32f-dbg_pyocd]
build_type = debug
board_upload.maximum_size = 32256 ; 512 bytes for config
debug_build_flags = ${common.debug_build_flags}
-DTESTO_REV1
[env:sc7-testobot_REV2-py32f-dbg_pyocd]
build_type = debug
board_upload.maximum_size = 32256 ; 512 bytes for config
debug_build_flags = ${common.debug_build_flags}
-DTESTO_REV2
@ -71,16 +71,16 @@ debug_build_flags = ${common.debug_build_flags}
[env:sc7-testobot_REV1-py32f-rel_bl]
build_type = release
board_build.ldscript = py32f030x6_bl.ld
board_upload.maximum_size = 30720 ; 2K reserved for bootloader
board_upload.maximum_size = 30208 ; 2K reserved for bootloader, 512 bytes for config
build_flags = ${common.build_flags}
-DBOOTLOADED
-DTESTO_REV1
[env:sc7-testobot_REV2-py32f-rel_bl]
build_type = release
board_build.ldscript = py32f030x6_bl.ld
board_upload.maximum_size = 30720 ; 2K reserved for bootloader
board_upload.maximum_size = 30208 ; 2K reserved for bootloader, 512 bytes for config
build_flags = ${common.build_flags}
-DBOOTLOADED
-DTESTO_REV2

View File

@ -30,7 +30,7 @@ _Min_Stack_Size = 0x100; /* required amount of stack: 256 bytes */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K - 512
}
/* Define output sections */

View File

@ -30,7 +30,7 @@ _Min_Stack_Size = 0x100; /* required amount of stack: 256 bytes */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
FLASH (rx) : ORIGIN = 0x08002000, LENGTH = 30K
FLASH (rx) : ORIGIN = 0x08000800, LENGTH = 32K - 2K - 512
}
/* Define output sections */

View File

@ -9,12 +9,12 @@
* voltage - only the analog supply can be the reference.
*
* because most inputs are high impedance, which is not ideal,
* sampling time is increased. this results in a final ADC sample rate
* of about 33KHz, still well beyond what is needed in this application.
* this value may change in the future.
* sampling time is increased. this value may change in the future.
*
* note: attempted to use DMA for this and scanning all channels
* at once, but that didn't work. would not reliably read channels.
* the example does 32bit transfers and I was doing 16bit;
* maybe there's some issue with that.
* tried many different variations and it just wasn't working.
* because of this I moved to using interrupt and the built-in
* wait mode just in case things went slow.
@ -30,7 +30,7 @@
#define SAMPLE_TIME LL_ADC_SAMPLINGTIME_239CYCLES_5
#define SAMPLE_TIME LL_ADC_SAMPLINGTIME_41CYCLES_5
#define ADC_SEQ_RDY 0xff
#define ADC_SEQ_STARTUP 0xfe
@ -44,7 +44,7 @@
static uint16_t adc_read[ADC_CHANNELS];
static uint16_t adc_hist[ADC_CHANNELS][ADC_HISTLEN];
uint16_t adc_avg[ADC_CHANNELS];
uint32_t adc_avg[ADC_CHANNELS];
static uint8_t adc_seq = 0;
static uint8_t adc_idx = ADC_HISTLEN + 1;
@ -70,6 +70,9 @@ void adc_init()
// with wait mode (anti-overrun) active
ADC1->CFGR1 = ADC_CFGR1_OVRMOD | ADC_CFGR1_WAIT;
// default clock
ADC1->CFGR2 = 0;
// configure scan channels, sampling time (11 = temp, 12 = vrefint)
ADC1->SMPR = SAMPLE_TIME;
ADC1->CHSELR = CONF_SET1_AN | PROBE_AN | // note: SET1 and VREFEXT are
@ -160,6 +163,9 @@ uint8_t adc_next()
// wait for calibration to complete, if started
while (ADC1->CR & ADC_CR_ADCAL) {};
// set ADC clock to 4MHz
ADC1->CFGR2 = LL_ADC_CLOCK_ASYNC_HSI_DIV2;
// do our first round of conversions
adc_go();
@ -185,7 +191,7 @@ uint8_t adc_next()
for (j = 0; j < ADC_HISTLEN; j++) {
adc_avg[i] += adc_hist[i][j];
}
adc_avg[i] >>= 4;
adc_avg[i] /= ADC_HISTLEN;
}
// check vref to determine if we need to recalibrate

View File

@ -15,6 +15,11 @@
#include "flash.h"
struct UConf conf = {0};
/*
* loads flash timing values.
* ensure flash is unlocked before calling this function,
@ -34,6 +39,7 @@ void flash_set_timing(uint32_t *timing_table)
// the values listed in the reference manual. this is true of
// all versions I have access to - English v1.0 and Chinese v1.2.
// for example, @8MHz, RM says 0x5dc0 for PERTPE but ROM holds 0x6b60
// in testing, either ROM or RM value seems to work.
// note: the datasheet doesn't say it anywhere, but if flash is not unlocked,
// then flash timing values aren't writeable
@ -84,8 +90,8 @@ __attribute__ ((long_call, section(".ramfunc"))) void flash_init()
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
// and signal that we want to write option bits,
// then trigger the write by writing to a random address shown in the DS
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_OPTSTRT;
// then trigger the write by writing to a random address shown in the DS
*((__IO uint32_t *)(0x40022080)) = 0x55aa55aa;
// wait for BSY to go low, then EOP to go high
@ -103,3 +109,242 @@ __attribute__ ((long_call, section(".ramfunc"))) void flash_init()
// re-lock flash when done
FLASH->CR = FLASH_CR_LOCK;
}
/*
* loads latest configuration from flash to conf variable in RAM.
*/
void flash_load_conf()
{
int8_t i, j;
uint8_t loaded = 0;
struct UConf *fconf;
uint32_t *f32;
uint32_t *u32;
for (i = (UCONF_COUNT - 1); i >= 0; i--) {
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * i));
f32 = (uint32_t *)fconf;
// empty flash = nothing here
if (fconf->crc32 == 0xffffffff) continue;
// invalid header = skip
if (fconf->conf_key != UCONF_KEY) continue;
// calculate crc32
CRC->CR = CRC_CR_RESET;
while (CRC->CR & CRC_CR_RESET);
for (j = 0; j < ((UCONF_SIZE / 4) - 1); j++) {
CRC->DR = f32[j];
}
// check crc32
if (CRC->DR == f32[(UCONF_SIZE / 4) - 1]) {
// it passes. copy to RAM and break out
u32 = (uint32_t *)&conf;
for (j = 0; j < (UCONF_SIZE / 4); j++) {
*u32++ = *f32++;
}
loaded = 1;
break;
}
}
if (!loaded) {
// default config of zeroes works
}
}
/*
* erases a flash page at the specified address.
*/
__attribute__ ((long_call, section(".ramfunc"))) void flash_erase_page(uint32_t addr)
{
uint32_t primask;
// ensure flash is not busy
while (FLASH->SR & FLASH_SR_BSY);
// unlock flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
while (FLASH->CR & FLASH_CR_LOCK); // still locked? then stall forever
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
// and signal that we want to erase a page
FLASH->SR = FLASH_SR_EOP;
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_PER;
// disable interrupts
primask = __get_PRIMASK();
__disable_irq();
// trigger the erase by writing to a random address shown in the DS
*((__IO uint32_t *)(addr)) = 0x55aa55aa;
// ensure flash is not busy, then wait for end of programming
while (FLASH->SR & FLASH_SR_BSY);
while (!(FLASH->SR & FLASH_SR_EOP));
FLASH->SR |= FLASH_SR_EOP;
// re-lock flash
FLASH->CR = FLASH_CR_LOCK;
// re-enable interrupts
__set_PRIMASK(primask);
}
/*
* erases a flash page at the specified address.
* data must be a pointer to a full page and
* must contain 128 bytes (32 words) of data to write.
*/
__attribute__ ((long_call, section(".ramfunc"))) void flash_write_page(uint32_t addr, uint32_t *data)
{
uint32_t primask;
uint8_t i;
uint32_t *src;
uint32_t *dst;
// ensure flash is not busy
while (FLASH->SR & FLASH_SR_BSY);
// unlock flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
while (FLASH->CR & FLASH_CR_LOCK); // still locked? then stall forever
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
// and signal that we want to write a page
FLASH->SR = FLASH_SR_EOP;
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_PG;
// disable interrupts
primask = __get_PRIMASK();
__disable_irq();
// write data to flash
src = data;
dst = (uint32_t *)addr;
for (i = 0; i < (FLASH_PAGE_SIZE / 4) - 1; i++) {
*dst++ = *src++;
}
// for the last word, enable the PGSTRT bit then write the word to commit to flash
FLASH->CR |= FLASH_CR_PGSTRT;
*dst = *src;
// ensure flash is not busy, then wait for end of programming
while (FLASH->SR & FLASH_SR_BSY);
while (!(FLASH->SR & FLASH_SR_EOP));
FLASH->SR |= FLASH_SR_EOP;
// re-lock flash
FLASH->CR = FLASH_CR_LOCK;
// re-enable interrupts
__set_PRIMASK(primask);
}
/*
* stores configuration from conf variable in RAM to flash.
* performs very basic wear leveling.
*/
void flash_commit_conf()
{
uint32_t i;
uint8_t flash_idx = 0;
uint8_t flash_full = 0;
struct UConf *fconf;
uint32_t *f32, *u32;
uint32_t write[(FLASH_PAGE_SIZE / 4)];
// find first free page index
for (i = 0; i < UCONF_COUNT; i++) {
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * i));
if ((fconf->crc32 == 0xffffffff) || (fconf->conf_key != UCONF_KEY)) {
flash_idx = i;
break;
}
}
// if all pages are occupied, we'll erase all the pages
if (i == UCONF_COUNT) {
flash_full = 1;
}
// ensure live config header is correct, and calculate CRC
conf.conf_key = UCONF_KEY;
conf.update_cntr++;
u32 = (uint32_t *)&conf;
CRC->CR = CRC_CR_RESET;
while (CRC->CR & CRC_CR_RESET);
for (i = 0; i < (UCONF_SIZE / 4) - 1; i++) {
CRC->DR = u32[i];
}
u32[(UCONF_SIZE / 4) - 1] = CRC->DR;
// do we need to actually update data?
// check checksum of our data against checksum of prior entry
if (flash_idx) {
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * (flash_idx - 1)));
if (fconf->crc32 == conf.crc32) {
// nothing to update; we can break out
return;
}
}
// set empty write buffer to 0xff
for (i = 0; i < 32; i++) {
write[i] = 0xffffffff;
}
// set write buffer
u32 = (uint32_t *)write;
// if this is the second entry in a flash page,
// copy first half of flash page from ROM to the write buffer,
// as our config size (64 bytes) is smaller than the page write size (128 bytes)
if (flash_idx & 1) {
f32 = (uint32_t *)fconf;
for (i = 0; i < (UCONF_SIZE / 4); i++) {
*u32++ = *f32++;
}
}
// correct flash_idx if it is out of bounds
// (we will usually have 1-8, only getting 0 on the very first write)
if (flash_idx == UCONF_COUNT) {
flash_idx = 0;
}
// copy user config from RAM to write buffer at the appropriate offset
f32 = (uint32_t *)&conf;
for (i = 0; i < (UCONF_SIZE / 4); i++) {
*u32++ = *f32++;
}
// we now need the flash page boundary, so remove lowest bit from flash_idx
flash_idx &= ~1;
// erase this page
flash_erase_page(UCONF_FLASH_START + (UCONF_SIZE * flash_idx));
// commit to flash
flash_write_page(UCONF_FLASH_START + (UCONF_SIZE * flash_idx), write);
// if flash was full, erase other pages
if (flash_full) {
for (i = (UCONF_FLASH_START + FLASH_PAGE_SIZE); i < FLASH_END; i += FLASH_PAGE_SIZE) {
flash_erase_page(i);
}
}
}

View File

@ -3,6 +3,8 @@
*
* your robot addon badge buddy for testing all the things
*
* version 0.0.1
*
* file creation: 20231015 0021
*/
@ -104,6 +106,9 @@ int main()
flash_init(); // also configures option bytes to enable PF2, if necessary
gpio_init();
// load configuration from flash
flash_load_conf();
// peripheral initialization
led_init();
adc_init();
@ -116,7 +121,7 @@ int main()
while (1) {
// run LED programs out of interrupt context at 256Hz
if ((ctr & 0xf) == 0) {
if (!(ctr & 0xf)) {
if (userio_get_mode() == MODE_FUN) {
rgbprog_run();
}
@ -130,6 +135,8 @@ int main()
/*
* main application interrupt
*/
uint16_t adc_ctr = 0; // count ADC cycles
volatile uint16_t adc_sec; // ADC cycles per second (used w/debugger)
void SysTick_Handler(void)
{
@ -144,16 +151,21 @@ void SysTick_Handler(void)
ctr &= 0xfff;
if (!ctr) {
uptime++;
adc_sec = adc_ctr;
adc_ctr = 0;
}
// run main logic at 1024Hz
if (!(ctr & 0x3)) {
// run main logic at 2048Hz
if (!(ctr & 0x1)) {
// shifted counter for use in the program
cs = ctr >> 2;
cs = ctr >> 1;
// adc tested to result in about 61 reads/second
if (!adc_next()) {
// adc has new computed results
adc_ctr++;
// start using ADC results only after the first cycle
if (uptime || cs) {
// figure out knobs, buttons, switches
userio_parse();

View File

@ -4,6 +4,9 @@
* this file is responsible for figuring out the
* continuity and diode tests from the raw analog data.
*
* todo: confirm mathematically that continuity values
* match the measured values
*
* file creation: 20231016 0255
*/
@ -13,77 +16,157 @@
#include "adc.h"
#include "led.h"
#include "rgbprog.h"
#include "userio.h"
#define CONT_OPEN 1240
#include "flash.h"
// diode measurements at 3V3
#define DIODE_OPEN 4000 // test unit measures ~4060
#define DIODE_STANDARD 2330 // fairchild 5V2 zener, forward biased
#define DIODE_SCHOTTKY 1960 // vishay 40V 1A SB140
#define DIODE_LED_BLUE 3190
#define DIODE_LED_GRN 3040 // very dim
#define DIODE_LED_RED 3020
#define DIODE_SHORT 1900 // test unit measures ~1820
#define DIODE_OPEN 3860 // test unit measures ~4060 no D1, ~3960 with D1
#define DIODE_SCHOTTKY 1960 // vishay 40V 1A SB140
#define DIODE_STANDARD 2330 // fairchild 5V2 zener, forward biased
#define DIODE_LED_BLUE 3190
#define DIODE_LED_GRN 3040 // very dim
#define DIODE_LED_RED 3020
#define DIODE_SHORT 1900 // test unit measures ~1820
#define SHORT_THRESHOLD 2
// continuity offsets from calibrated short value
#define CONT_47OHM 16
#define CONT_100OHM 34
#define CONT_EXTRA_COUNTS 3 // extra raw counts as a fudge factor
#define CONT_CAL_THRESHOLD 890 // typical short is ~845-860 region
#define CONT_LATCH_THRESHOLD 5 // extra raw counts for latching indicator
#define CONT_LATCH_TIMEOUT 4 // probe measurement passes to hold latched indicator
static uint16_t vref;
static uint8_t cont_cal_timer;
static uint16_t cont_cal_low;
static uint16_t cont_cal_avg;
static const uint16_t cont_thresh_tbl[] = {
CONT_100OHM, CONT_47OHM
};
static uint8_t latch = 0;
static uint8_t buzzer = 0;
static inline void probe_measure_cont()
{
uint16_t probe, v_ext;
uint32_t x;
uint32_t x, c;
uint8_t g, b;
probe = adc_avg[ADC_PROBE];
v_ext = adc_avg[ADC_VREF_EXT];
// if the button has been pushed, toggle the buzzer
if (userio_get_btn() > 0) {
buzzer ^= 1;
}
// this LED will not set anything,
// but zero it out anyway
led_setrgb(1, 0, 0, 0);
if (vref) {
x = probe << 12; // maximum possible level
x /= vref; // normalize to 4096max
if (x > 4095) x = 4095;
if (x < 2405) { // roughly 100ohm or lower
latch = 4; // latch any continuity for a while
}
if (latch) {
// indicate continuity
if (x >= 2420) latch--; // hysteresis
led_setrgb(0, 0, 250, ((buzzer) ? 100 : 0));
led_buzz(0);
if (buzzer) led_buzz(1);
} else {
// idle
led_setrgb(0, 120, 0, ((buzzer) ? 8 : 0));
led_buzz(0);
}
}
// update vref
if (!vref) vref = v_ext;
vref += v_ext;
vref >>= 1;
// if the button has been held, change the threshold
if (userio_get_btn_held() == 60) {
userio_set_btn_override();
conf.cont_sensitivity ^= 1;
}
// if the button has _really_ been held, clear the calibration
if (userio_get_btn_held() == 600) {
conf.cont_shorted = 0;
}
// if there is no calibration, try to perform it
if (!conf.cont_shorted) {
// until this is performed, show error LEDs
rgbprog_error_flasher(5, 0, 0, 100);
// initialize
if (!cont_cal_timer) {
cont_cal_low = 0xfff;
cont_cal_avg = 0;
}
// if probe appears to be shorted, calibrate to lowest value read
if (adc_avg[ADC_PROBE] < CONT_CAL_THRESHOLD) {
// low value
if (adc_avg[ADC_PROBE] < cont_cal_low) {
cont_cal_low = adc_avg[ADC_PROBE];
}
// average value
if (!cont_cal_avg) {
cont_cal_avg = adc_avg[ADC_PROBE];
} else {
cont_cal_avg += adc_avg[ADC_PROBE];
cont_cal_avg >>= 1;
}
cont_cal_timer++;
} else {
cont_cal_timer = 0;
}
// after held long enough, commit
if (cont_cal_timer == 180) {
cont_cal_timer = 0;
conf.cont_shorted = (cont_cal_avg + cont_cal_low) >> 1;
flash_commit_conf();
}
}
// right LED remains unused while buzzer is being used,
// but zero it out anyway
led_setrgb(1, 0, 0, 0);
// if no calibration yet, bail here
if (!conf.cont_shorted) return;
// if the button has been pushed, toggle buzzer on/off
if (userio_get_btn() > 0) {
conf.cont_buzzer ^= 1;
}
if (vref) {
// note: we don't use VREF anymore...
// it's too noisy :/
// probe level
x = probe - conf.cont_shorted;
if (x > 0xfff) x = 0;
// threshold level
c = cont_thresh_tbl[conf.cont_sensitivity];
// are we measuring a lower ohm value on the probes than our threshold?
if (x < (c + CONT_EXTRA_COUNTS)) { // roughly 100ohm or lower
latch = CONT_LATCH_TIMEOUT; // latch any continuity for a while
}
// blue led to indicate buzzer is enabled
b = conf.cont_buzzer ? 20 : 0;
if (latch) {
// hysteresis
if (x >= (c + CONT_LATCH_THRESHOLD)) latch--;
// indicate continuity
led_setrgb(0, 0, 250, b);
led_buzz(0);
if (conf.cont_buzzer) led_buzz(1);
} else {
// idle
g = conf.cont_sensitivity ? 40 : 0;
led_setrgb(0, 120, g, b);
led_buzz(0);
}
}
}
static inline void probe_measure_diode()
@ -97,23 +180,23 @@ static inline void probe_measure_diode()
if (p < DIODE_SHORT) {
// show off on short
led_setrgb(1, 0, 0, 0);
}
} else
if (p >= DIODE_OPEN) {
// show red on open
led_setrgb(1, 120, 0, 0);
}
} else
if (p >= 1940 && p < 2650) {
// show green on regular or schottky diodes
led_setrgb(1, 0, 200, 0);
}
if (p >= 2650 && p < 3800) {
} else
if (p >= 2650 && p < 3700) {
// show blue on LEDs
led_setrgb(1, 0, 0, 500);
} else
if (p >= 3701) {
// show red+blue if between open and LED
led_setrgb(1, 120, 0, 120);
}
// if we haven't set a value, flash red to indicate error state
}
void probe_measure()
@ -124,7 +207,7 @@ void probe_measure()
switch (mode) {
case MODE_CONT: probe_measure_cont(); break;
case MODE_FUN: break; // todo: run RGBLED program
case MODE_FUN: break; // LED program is run elsewhere
case MODE_DIODE: probe_measure_diode(); break;
}
}
@ -135,13 +218,22 @@ void probe_measure()
*/
void probe_mode_switch(uint8_t mode)
{
if (mode == MODE_CONT) {
// set LED to buzzer mode, set PROBESEL low
led_mode_buzzer();
PROBESEL_PORT->BRR = (1 << PROBESEL_PIN);
} else {
// set LED to dual RGB mode, set PROBESEL high
led_mode_rgb();
PROBESEL_PORT->BSRR = (1 << PROBESEL_PIN);
switch (mode) {
case MODE_CONT: {
// set LED to buzzer mode, set PROBESEL low
led_mode_buzzer();
PROBESEL_PORT->BRR = (1 << PROBESEL_PIN);
break;
}
default: {
// set LED to dual RGB mode, set PROBESEL high
led_mode_rgb();
PROBESEL_PORT->BSRR = (1 << PROBESEL_PIN);
}
}
// save config if moving into LED mode
if (mode == MODE_FUN) {
flash_commit_conf();
}
}

View File

@ -23,6 +23,8 @@
*/
#include "rgbprog.h"
#include "led.h"
#include "hsv2rgb.h"
#include "rand.h"
@ -112,6 +114,13 @@ void rgbprog_run()
brite &= 0x3;
}
// if SET1 is out of range, indicate that instead of
// running the normal program
if (userio_get_set1_limit()) {
rgbprog_error_flasher(0, 120, 0, 0);
return;
}
// which program to run?
j = 0;
for (i = 32; i <= 256; i += 32) {
@ -390,3 +399,26 @@ void rgbprog_prog7(uint8_t k0)
rgb_setled(0, &rgb[0]);
rgb_setled(1, &rgb[0]);
}
/*
* internal program:
*
* flashes LEDs, indicating a SET1 knob error.
*/
static uint8_t err_ctr = 0;
static uint8_t err_tog = 0;
void rgbprog_error_flasher(uint8_t scale, uint8_t r, uint8_t g, uint8_t b)
{
uint8_t x;
x = (1 << scale);
err_ctr += x;
err_ctr &= ~(x - 1);
if (!err_ctr) {
err_tog ^= 1;
}
led_setrgb(err_tog, r, g, b);
led_setrgb(err_tog ^ 1, 0, 0, 0);
}

View File

@ -41,8 +41,13 @@
#define MODE_ANALOG_MIN 20
#define BTN_OPEN 0
#define BTN_PUSHED 1
#define BTN_OVERRIDE 2
#ifdef TESTO_REV1
#define SET1_MAX 2047 // 3V net feeding into 1V24 via body diode above this
#define SET1_MAX 2199 // 3V net feeding into 1V24 via body diode above this
#else
#define SET1_MAX 1539 // 1.24Vref
#endif
@ -76,9 +81,12 @@ void userio_parse()
m = adc_avg[ADC_SET_MODE];
if (m < MODE_ANALOG_MIN) {
// button is pushed
btn = 1;
if (btn == BTN_OPEN) btn = 1;
if (btn_held != 0xffff) btn_held++;
} else if (btn == 1) {
} else if (btn) {
// release held count if overridden
if (btn == BTN_OVERRIDE) btn_held = 0;
// button is released
btn = 0;
}
@ -165,8 +173,8 @@ int16_t userio_get_btn()
{
int16_t ret;
if (btn) return -1;
if (!btn) {
if (btn == BTN_PUSHED) return -1;
if (btn == BTN_OPEN) {
if (btn_held) {
ret = btn_held;
btn_held = 0;
@ -176,3 +184,34 @@ int16_t userio_get_btn()
return 0;
}
/*
* returns the button state.
* 0 = not held
* >0 = held for X ticks
*/
int16_t userio_get_btn_held()
{
return btn_held;
}
/*
* overrides and disables the next button release event.
*/
void userio_set_btn_override()
{
btn = BTN_OVERRIDE;
}
/*
* returns true if the right knob is beyond maximum range.
* always returns false on REV2 or later boards.
*/
uint8_t userio_get_set1_limit()
{
#ifdef TESTO_REV1
if (adc_avg[ADC_SET1] > SET1_MAX) return 1;
#endif
return 0;
}