Thursday, January 14, 2016

Lessons in buying Bitcoin


While bitcoin is far from mainstream, with it making headlines like Mark Zuckerberg's nemesis twins Tyler and Cameron launching Gemini, I figured I'd learn how to use bitcoin.  Aside from nerds using it to tip on reddit and github, bitcoin doesn't have much practical use.  Personally my interest is primarily educational, so if any bitcoin related business opportunities arise in the future, I may be able to capitalize on them.

With their logo at the start of this post, you can probably guess that I recommend coinbase for Canadians and Americans looking to buy small amounts of bitcoin (under US$500 worth of bitcoin).  This recommendation comes after I've looked at the offerings from several bitcoin exchanges including bitstamp, bitfinex, Kraken, cex.io, and Canadian exchange QuadrigaCX.  I registered for an account at bitstamp and coinbase, and traded bitcoin on the latter.

Bitstamp supports funding (sending money to bitstamp so you can buy bitcoin) from Canadian bank accounts.  Any funds are converted to USD, but finding out the exchange rate takes some work.  I emailed bitstamp on Christmas eve asking for their foreign exchange fees, and received a reply back on the 28th:
to view our exchange rates, please see the following link and click on the "Corporate exchange rates" for the correct rates: http://www.raiffeisen.si/en/pripomocki/exchange_rates/ .Please note that all currencies are converted to USD free of charge by our bank.
I checked the exchange rates, and found that their bank adds about 0.6% to the spot rate for CAD/USD.  On top of that you'd have to add their trading fee of 0.1% for a limit order or 0.2% for a market order.  Adding that to their $1 minimum e-check fee means that the total fees to buy $100 in bitcoin would be $1.70.  That's reasonable compared to most other exchanges, but you'll have to pass their account verification first.  Despite providing a 300dpi high-quality jpeg scan of my driver's license, my account verification request was denied with the message, "the quality of the image/scan cannot be accepted according to UK AML standards."  In other words, no bitstamp for me!

For coinbase I couldn't completely figure out their fees until I actually set up an account.  Some of their support pages refer to 0% maker and 0.25% taker fees on their exchange.  Other support pages will refer to a 1% fee for buying bitcoin.  In the end I figured out both are correct, since there are two ways to buy and sell bitcoin.

With coinbase, unlike bitstamp, you can fund your account without ID verification.  You will need a cell phone with a US or Canadian number for a basic residency check.  Once your account is setup and you choose to deposit funds to your "CAD wallet", you are presented with the following options:

When I chose "Deposit with Interac", I wasn't able to proceed and was given an error message that I need to verify my account.  A good account interface design wouldn't have presented the option, and instead would have it greyed out with a note that the option is available after ID verification.  To use the bank account, you need to provide your bank, transit, and account number.  After a day or two you'll see a small deposit to your account (for me it was under 50c).  You need to log into your online banking to see the amount of the deposit, and then enter the exact amount in your coinbase account to link with your bank account.  Once that is done, you can take funds from your bank account, and after a few days the funds will become available for buying bitcoin.

Being a bit impatient, I decided to provide ID verification so I could use Interac Online in order to get funds instantly into my account.  A few minutes after uploading same jpeg file of my license that bitstamp refused to accept, I got an email stating my identity has been verified.  In addition to making the Interac Online available, verifying my account increased my limits from $500/day to $3000/day (not that the $500 limit was a problem for me).  However I still couldn't use Interac Online because, "Interac online is not available for visa debit card holders."  If you have a relatively new bank card (issued in the last couple years) with the Visa debit logo in the corner, you're out of luck.

My wife's account, however, doesn't have the Visa debit logo.  After noticing this, and reading about the referral program that gives $10 in BTC for the new account and for my account, I set up another account for my wife.  Now that I knew how coinbase worked, the process was a lot quicker.  I helped he set up the account, and had her account verified with a copy of her license.  Then I used Interac Online to withdraw C$149 from her account, leaving C$148 in her CAD wallet after the $1 fee was deducted.  I then chose to buy bitcoin, entered $148, which left $146.52 after the 1% fee, and completed the transaction at the quoted exchange rate (about C$620/BTC).  A few minutes later I got an email about my invitation bonus, and at the same time an additional US$10 worth of BTC showed up in my wife's account.

So what about those 0% maker and 0.25% taker fees?  For that you need to use the coinbase exchange, which you can do by clicking on "exchange" from your account, and then click on log in with coinbase.  The interface is similar to a discount stock broker, with a list of bid and ask prices along with a calculation of the current spread.  With a Canadian account you can only trade CAD/BTC, but you can view the USD/BTC order book.  The spreads on the USD/BTC exchange are usually only 1-2c, while spreads of $1-$2 are common for CAD/BTC.   Because of that I was able to get better prices than I could if I was trading USD/BTC.  I tried a couple small (<0.1BTC) limit sell orders, with a price a few cents below the lowest listed sell order price.  The first one filled in about 10 minutes, and the second filled in less than a minute.  Both, as expected, had no fees as "maker" orders.

As long as the invitation program continues, the net fees to get C$160 in bitcoin is actually negative.  After paying $2.50 in fees to buy $146 in bitcoin, you'll get US$10(C$14) in bitcoin as a bonus.  Probably not worth the trouble for most people, but certainly worth it for the nerds and geeks that want to give out bitcoin tips.

Monday, January 11, 2016

ESP8266 reset and booting


When I got my first esp-01 module, I found there was a lot of mis-information about how to reset and flash the modules.  For the esp-01, pulling GPIO0 low while pulsing reset low is the easy and common way to get it to enter the bootloader for flashing.  When using modules like the ESP-12 that have most of the available pins broken out, things get a little more complicated.

I noticed the issue when I tried wiring a module for deep sleep mode by connecting GPIO16 to RST.  In deep sleep mode only the RTC is running, and instead of an internal software wake-up timer like some other MCUs, the esp8266 has to be woken up by a pulse on RST generated by the RTC on GPIO16.  With the GPIO16 to RST connection, I had difficulty resetting the module with a 270 Ohm resistor to ground.  The reason is that GPIO16 is not a pull-up, it is in output high mode.  The solution is to reboot the module using the EN pin (also labeled CH_PD on some modules) instead of RST.  The difference between RST and EN is that bringing EN low powers down the whole chip including the RTC.

I did some additional testing of the chip boot-up by holding RST low and EN high.  While in reset, the chip keeps GPIO0, GPIO2, and GPIO15 high with an internal 33K pull-up to Vcc.  If reset is then released, the chip will boot in SDIO mode, which is really only good for hooking it to a raspberry pi.  ESP-01 modules have GPIO15 connected to ground, but for modules like the ESP-12 it is necessary to pull GPIO15 low during boot.

After booting up, I checked the default state of most of the pins.  TX0 is high, which is the UART idle state, while RX0 has a weak pull-up so that it is not floating.  GPIO0 has a weak pull-up, GPIO2 is high, GPIO4 is low, GPIO5 is high, and GPIO15 stays low, even with a pull-down resistor.

With ESP-01 modules, I was using my zero-wire auto-reset circuit with DTR connected to GPIO0 in order to flash the modules.  For projects that were using GPIO0, this meant having to disconnect the DTR line after flashing.  I'm now working on a circuit that doesn't require DTR, and will hold GPIO0 and GPIO15 low for a short while after the module is rebooted by toggling EN.

I haven't found any information that indicates how long GPIO0 and GPIO15 need to be held low in order to enter the bootloader, so I'll need to experiment with the resistor and capacitor values.  Using a 15K resistor and 0.1uF capacitor keep GPIO0 and GPIO15 low for about 1ms after startup.

Sunday, January 3, 2016

A 3.6V LiFePO4 charger for under 50c


I like LiFePO4 batteries.  They have a rather flat discharge at around 3.2V, which is ideal for powering 3.3V devices without a regulator.  You can also use them in devices that take 2 standard AA cells by using a blank shunt in the 2nd battery slot since 2 fresh alkaline cells in series provide 3.2-3.3V.  And since they are readily available in the 14x50mm AA size, you can use cheap AA holders for them in electronics projects.

When it comes to chargers, things can be a bit problematic.  LiFePO4 batteries should be charged to 3.6V, rather than 4.2V like regular lithium-ion batteries.  A good charger costs $10-$15, but charging at a high current will reduce the number of recharge cycles.  The Soshine batteries I bought indicate on the label a standard charging current of 300mA to 3.6V.  Rather than search for a charger to fit the bill, I decided to make one.

From my experiments with TL431 regulators, I remembered the regulator circuit above.  My idea was to take a 5V USB supply, and regulate it to 3.6V for charging the LiFePO4 cells.  For the NPN transistor, I wanted to use something more powerful than the usual 2N3904 which has a collector current rating of 200mA.  I found some old PN2222a transistors which are rated for 600mA, more than enough for the 300mA I needed.  Another thing to keep in mind when choosing a transistor is power dissipation.  TO92 packages are good for about 500mW before they get hot enough to burn flesh.  With the voltage drop across the transistor around 1.45V (5.05V - 3.6V), up to 350mA should be fine with a TO92 transistor.

Having chosen the transistor, I needed to figure out R1 and R2 such that I would get the desired 3.6V at Vout.  With Vref = 2.5, R1/R2 = 0.44, I needed to find 2 resistors close to that ratio.  After sorting through my resistor collection, I found some 1.2K and 2.7K resistors, which gives a ratio of 0.444.  The capacitor between the reference and cathode of the tl431 can be omitted, so the only other component I needed to figure out was unlabeled resistor between the transistor collector and base.  The amount of base current required will depend on the gain of the transistor.  With 300mA of collector current  the gain could be anywhere from 15 to 50.  I had some 68Ohm resistors handy which I decided to try.  Vbe of the transistor is around 0.7V, so with V+ at 5.05 and Vout at 3.6, using a 68Ohm resistor would give me a base current of 11mA.  I tested it with a partially discharged battery, and measured 290-300mA!  Things weren't quite perfect though, as after 10 minutes the voltage passed 3.6, and kept rising toward 4V.  I disconnected the power, and after some debugging I realized I had R1 and R2 mixed up.  I discovered this by measuring the reference voltage, and finding it was around 1.3V when Vout was 4.2.  After swapping the resistors, with no battery connected I was getting 3.61 for Vout and 2.5V on the reference - just what I wanted.

One other word of warning - be careful using the cheap little 170-hole breadboards for projects with more than 20-30mA of power.  I had started with one of them until I remembered the problematic high-resistance contacts on them.

Before getting out the soldering iron and making the circuit permanent, I wanted a way to tell when charging was complete.  After toying with a couple ideas, my final solution is a 2V green LED in series with the TL431 cathode.  When Vout reaches 3.6, the tl431 shunts almost all of the base current (a small base current is still required due to the ~1mA of leakage through R1 + R2).  When I tested it out, I found an unexpected benefit of the LED glowing dimly as charging starts, and then turning bright when charging completes.  This can be explained by looking at the cathode current graph from the datasheet:

With a fully-discharged battery, Vout will start at around 3V, and Vref will be about 2.1V.  The cathode current will be about 250uA, and since the LED is in series with the cathode, that will be the LED current as well.  This is enough to make a dim but visible glow.  When Vout reaches 3.6V, the tl431 will shunt most of the 11mA from the transistor base, making the LED glow brightly.

The last thing I tried while I had everything in the breadboard was substituting at 2N3904 for the PN2222a.  Suprisingly, I was able to get 200mA of charging current.  So if all you have are 3904s, you can still get a reasonable amount of current.  You could probably even use 2 3904s in parallel for close to 400mA of current.

For the permanent version of the circuit I used a small 7x4 piece of stripboard.  By planning out the layout on paper first, I was able to use 7 strips without any breaks.  I got a little too rushed doing the assembly and put the tl431 in the wrong way around (cathode and reference swapped).  After fixing it, I tested for continuity on all my solder connections. One needed to be re-worked, and with a bit of flux, a dab more solder, everything was good.  After some hot glue to attach the battery holder, I plugged it into a USB power port, and it worked perfectly.

Here's my final BOM:
AA battery holder: 15c
1/12th of a stripboard: 6c
USB male connector: 22c ( or 10c from other sources)
Old pn2222a: 5c (replacement cost; paid $3 for a package of 15 at RadioShack almost 30 yrs ago)
TL431A: 1.5c
resistors: 3c
3mm green led: 1c

Satisfaction of building your own battery charger: priceless :-)

2015/01/05 update

I tried doing the stripboard layout in fritzing, but it doesn't have a tl431, and I couldn't get a good view given the perspective it uses.  Instead I tried drawing my layout plan in a more readable form:

Friday, December 25, 2015

Bitcoin mining hardware for Christmas?

Since I was a teenager, I've been interested in cryptography.  I've also been interested in business and finance, and started my first business selling computer accessories in high school.  Bitcoin, as a crypto-currency, is something that certainly intrigues me.  Recently I decided to do some research on bitcoin mining.  It would seem there may still be opportunities for individuals to make money mining bitcoins.

The image above is of a Antminer S7, which today (Dec 25) sells for 2.91BTC.  One bitcoin is trading for about US$455, making the price of the miner about US$1325.  If you are willing to wait for the Jan 20th production batch, the price drops to 2.4BTC.  The only other bitcoin miner on the market at this time is the Avalon 6, which sells for 3.2BTC.  Rated at 3.65 trillion hashes per second (TH/s), the Avalon is much less powerful than the Antminer at 4.73 TH/s.

So can you make money with bitcoin mining hardware?  Probably, but it depends on where you live, or more specifically, what you pay for electricity.  It also depends on fluctuations in the bitcoin market including the price of bitcoin.  I'll explain my guess on where the market will go, and do the math on whether buying a S7 would be profitable where I live in Eastern Canada.

The first thing to work out is the landed cost of the S7.  Allowing about $50 for shipping and adding local taxes that would be due on import brings the cost up to $1575.  Adding three 500W power supplies (cheaper than a single 1500W supply) brings the total to $1725.  From there I could create a complex spreadsheet to calculate predicted mining difficulty increases and power costs, but it is a lot easier to use an online mining mining calculator.

The first field in the form is the difficulty increment, which I'll leave to the end as it is a bit complicated.  While the average cost of electricity in North America is close to 10c/kWh, where I live it is about 12c US, so 0.12 goes in the electricity price box.  I think the Antpool fees are less than 2%, but I left in the default 2% just to be safe.  The hash rate for the S7 is 4730 GH/s, and my previously-calculated hardware price was $1725.  Power consumption is around 1250W.  I expect it would take about a week from the time payment is made until the S7 would be up and running, so I put in 7 days for the start date.

Now for that complicated difficulty increment.  Although the form defaults to 20%, that is much higher than the average over the past year.  .To estimate the expected difficulty increase with a constant price, I looked at mid-February and mid-October 2015, when the price was about US$250/BTC.  The difficulty increase during that time was 30%, and averaged over the 17 difficulty adjustment intervals, that works out to 1.56%.  I rounded that up to 2%, in case the increase in difficulty in the future is slightly faster.  Based on those numbers, the S7 would start generating a profit in November of 2016.

Another complication to estimating mining profitability is the block reward halving that will likely happen by July 2016.  At that time the reward for mining will drop from 25BTC per block to 12.5BTC per block.  I suspect that will impact the economics of mining so that it would take about a year to start generating a profit instead of  about 10 months.

My conclusion from all this: If you bought yourself a bitcoin miner for Christmas, I think the odds are better than 50/50 that you'll make money on your investmentbet within 2 years.  Personally, I'd put my money in a low-risk investment that is likely to return 5-7%, or at least wait and see if the economics of bitcoin mining significantly changes.

2015/12/31 update

I just noticed the specs on the recent batches of S7s has changed.  The original specs were 4.86 TH/s using 162 chips (54 per board) running at 600Mhz and drawing 1210W with a 93% efficient PSU.  The chips were configured in 3 chains of 18 chips per board, running at 0.667V ea (12V/18).  The newest S7s now have 135 chips (45 per board) running at 700Mhz and drawing 1293W, generating 4.73TH/s.  This likely means there are 3 chains of 15 chips per board, running at 0.8V.  The higher voltage allows for a higher clock rate, but at the cost of power efficiency, going from 249W/TH to 273W/TH.

I suspect two reasons for the change.  The BM1385 chips (pdf datasheet) were not always stable running at 600Mhz, so Bitmain changed the default to 575Mhz on batch 2 and batch 4 (for 4.66 TH/s).  Running at a higher voltage allows the chips to run at a higher clock rate.  The second reason is probably cost reduction; they now can produce 20% more S7s than before for a given number of ASICs.  For the short term I think that is a good idea since the S7 is still the most power-efficient miner available.  Before more power-efficient equipment using 16nm mining ASICs starts shipping, it may make sense for Bitmain to make another version of the S7 with 4 chains of 20 chips per board running at 0.6V and 400Mhz.  The power consumption would be about 1000W for 4.8TH/s (208W/TH), and they would be more reliable running at lower voltages and cooler temperatures.

Sunday, December 20, 2015

$6 AD584KH voltage reference


After buying a couple cheap REF5050s that turned out to be no good, I decided to buy a $6 AD584KH voltage reference.  A new AD584 costs about $20, so getting a whole module for $6 may seem like a scam at first.  However a close look at the photos from the vendor, and the photo of the one I received reveal that the AD584KH used on these modules is not new.  The one I received has a date code of 9703, so these are used parts, likely de-soldered from old equipment.  For voltage references, used parts are usually better than new, because their stability improves after they are aged for 250-1000 hours.  References that are hermetically sealed in a metal can like the AD584 are even more stable than the REF5050 since changes in humidity do not reach the IC.  As can be seen from the calibration sticker on the anti-static bag, the output voltages were measured with a 6-digit bench meter.

The reference was useful for testing and calibrating my 2 auto-ranging meters.  I adjusted my old meter so that it now reads about 0.025% high on the 0-4V range.  My new meter doesn't have any calibration pots, but I was able to determine that it reads about 0.11% low on the 0-6V range.  I've also concluded that my Wing Shing TL431A parts are 2.5V, not 2.495.  This is not too surprising, as many other 431 parts are 2.5V, like the LT1431 and the CJ431.  I also checked a cheap 3-digit voltage meter, and found it was reading low by about 0.5%; not bad considering it costs not much more than a dollar.

Spending $6 to check a $25 meter is probably not something most people will do.  Although I don't need to be able to measure voltages down to the nearest millivolt, the perfectionist in me likes that kind of precision.  And with only occasional use, it is likely to maintain better than 0.01% (100ppm) accuracy for decades to come.

Thursday, December 17, 2015

Lightweight AVR assembler functions

Over the past few years, I've written a few posts about small problems in the way avr-gcc generates code.  These problems are typically poor optimization, and since they don't cause problems in program functionality, they tend not to be fixed.  I believe the biggest opportunity in optimization is in inter-procedure register analysis, which is being worked on in GCC 5, but is unlikely to support 8-bit AVR MCUs.  Therefore I have developed a lightweight ABI for calling assembler functions:

inline void eelog(char logdata)
{
    // no output, x register input, no clobbers
    asm volatile (
    "rcall eelog_\n"
    :
    : "x" (logdata)
    : );
}

Before explaining the above code, it helps to understand the standard avr-gcc register usage.  Functions are free to use registers r18-r27 and r30-31, which means that if the code the calls a function is using any of these registers, they need to be saved before calling the function.   This applies even if the called function doesn't even modify any of the registers, since the compiler doesn't do inter-procedure register analysis.  The following small program and assembler code shows what I mean:

void main(void)
{
    volatile uint8_t* ioreg;
    uint8_t offset = 0x3f;
    eelog(42);
    ioreg = (uint8_t*)0x0020;
    do {
        eelog(*(ioreg + offset));
    } while ( offset-- );
}

00000092 <main>:
  92:   cf 93           push    r28
  94:   8a e2           ldi     r24, 0x2A       ; 42
  96:   f4 df           rcall   .-24            ; 0x80 <eelog>
  98:   cf e3           ldi     r28, 0x3F       ; 63
  9a:   ec 2f           mov     r30, r28
  9c:   f0 e0           ldi     r31, 0x00       ; 0
  9e:   80 a1           ldd     r24, Z+32       ; 0x20
  a0:   ef df           rcall   .-34            ; 0x80 <eelog>
  a2:   c1 50           subi    r28, 0x01       ; 1
  a4:   d0 f7           brcc    .-12            ; 0x9a <main+0x8>
  a6:   cf 91           pop     r28
  a8:   08 95           ret

The compiler uses r28 for the offset counter, since the function being called is not allowed to use r28/r29 (aka the Y register).  Since r30/31 (Z) can be modified (clobbered) by the function, the Z register needs to be initialized each time through the loop (at 0x9a and 0x9c).  The single parameter is passed in r24.

Although I've been programming in AVR assembler for a few years now, it has taken me a while to learn inline asm.  I still prefer writing in plain asm, but inline asm is the only way I've found to call assembler functions from C without having to follow the standard calling convention.  The sample code at the start of this post defines a function named eelog that takes a single parameter passed in r26 (the low byte of the X register).  Using this technique, the compiler can safely use r24 for the offset counter instead of r28:
00000080 <main>:
  80:   aa e2           ldi     r26, 0x2A       ; 42
  82:   08 d0           rcall   .+16            ; 0x94 <eelog_>
  84:   8f e3           ldi     r24, 0x3F       ; 63
  86:   e8 2f           mov     r30, r24
  88:   f0 e0           ldi     r31, 0x00       ; 0
  8a:   a0 a1           ldd     r26, Z+32       ; 0x20
  8c:   03 d0           rcall   .+6             ; 0x94 <eelog_>
  8e:   81 50           subi    r24, 0x01       ; 1
  90:   d0 f7           brcc    .-12            ; 0x86 <main+0x6>
  92:   08 95           ret

A careful reader will notice that more optimal code would use r30 for the offset counter, but even recent versions of avr-gcc aren't that smart.  Given other examples of poor optimization have persisted for several years, it is unlikely avr-gcc will get as good as hand-written asm anytime soon.  Another thing sharp readers may notice is that the volatile keyword in the "asm volatile" statement is superfluous because asm statements that have no output operands are implicitly volatile.  I think it is best to leave it in, in case the function is later changed to have an output operand, or if it is used as a template for another function.

If anyone is wondering why I didn't make a simplified version of the the standard calling convention that just used r24/25, it is because there is no inline asm constraint for r24/r25.  The constraint "w" might get the compiler to use r24/25, but it also could use r26/27 (X), r28/29 (Y) or r30/31 (Z).

Conclusion

Using this technique can significantly reduce register pressure on the compiler, which will reduce code size and increase speed.  Inline asm constraints could also be used for a function that returns multiple values, without having to use a C struct.  Code can be found in my new avrutils repository.


Sunday, December 6, 2015

Piggy-prog release version


Over the past few months I've been making progress with piggy-prog and picoWiring.  Since the beta version of piggy-prog, I've made a couple changes.  The first is a 8Mhz clock on PD6 (Arduino pin 6) using PWM.  That pin connects to physical pin 2 of the AVR 8-pin AVR underneath, which is the clock in (CLKI) pin on the ATtinyx5 and ATtiny13.  This allows programming AVRs that (sometimes accidentally) have the fuse settings for external clock.

The second change I've made is to reduce the SPI SCK timing to 128kHz to allow for programming AVRs with the CKDIV8 fuse set (factory default).  According to the datasheet specifications a 250kHz clock should work, but a few ATtiny13s I tested required a 128kHz clock to work when the CKDIV8 fuse was set.

To build piggy-prog you can download piggy-prog.ino and upload it to a Pro Mini using the Arduino or Wiring IDE.  If you have avr-gcc and avrdude installed, you can download the release, and run "make avrdude" to compile and flash the code to the Pro Mini.  Plug the Pro Mini into a breadboard so pins 9 and 10 are 1 row below pins 4 and 5 of the ATtiny, and pins 5 and A0 are in the same row as pins 1 (RST) and 8 (VCC) of the ATtiny.  Power will be supplied by the Pro Mini, so no jumper wires are needed.

In addition to working with the ATtinyx5 and ATtiny13 MCUs, it should also work with the ATtiny2313.  Even though pin 4 is not GND on the tiny2313, the input clamping diodes will make the ground connection.  The diode voltage drop of about 0.7V will mean the power to the tiny2313 will be about 4.3V, but that is still well within the operating range of the chip.

For the next version of piggy-prog, I plan to add detection of the position of the attiny, so that if it is one row up or down on the breadboard, piggy-prog will indicate that the chip needs to be moved.  This detection will be done by putting a weak pull-up voltage on one of the pins and then detecting if the other pins go high.  Since the RST pin does not have a VCC clamping diode, it will also be possible to safely detect if  the chip is in backwards (where VCC and GND are swapped).  For version 1, pay attention to the position of pin 1 (with a round dot close to it), as it is possible (in theory) to damage both the Pro Mini and the target MCU if you put the ATtiny in backwards.  I have accidentally reversed VCC and GND on AVRs without any apparent damage, but YMMV.