Maxwell’s Equations

<long unrelated intro>

I want to write a general Maxwell solver.

Energy and momentum are carried by electromagnetic fields through the Poynting vector and the energy density:

\(\vec{p}_{em}=\vec{S}/c^{2}=(1/\mu_{0})\vec{E}\times\vec{B}\)

\(u_{em}= (\epsilon_{0}/2)E^{2}+(1/2\mu_{0})B^{2}\)

They follow the following conservation laws:

\(\frac{\partial u_{em}}{\partial t}+\nabla\cdot\vec{S}+\vec{J}\cdot\vec{E}=0\)

\(\frac{\partial \vec{p}_{em}}{\partial t}-\nabla\cdot\sigma+\rho\vec{E}+\vec{J}\times\vec{B}=0\)

I was thinking it would be neat to have a finite volume method solver for Maxwell’s equations in terms of these conservation laws, but this is a flawed conception as the energy equations aren’t closed and IMO any way to close them ruins the neatness. So instead I decided to take the exact opposite extreme and just play around with a finite difference scheme.

The electric and magnetic fields can be written in terms of the potentials \(\phi\) and \(\vec{A}\). In the Lorenz gauge, Maxwell’s equations are:

\(\nabla^{2}\phi-1/c^{2}\frac{\partial^{2} \phi}{\partial t^{2}}=-\rho/\epsilon_{0}\)

\(\nabla^{2}\vec{A}-1/c^{2}\frac{\partial^{2} \vec{A}}{\partial t^{2}}=-\mu_{0}\vec{J}\)

Which is the inhomogenous wave equation four times. I found a stencil for the 3D Laplacian in [R.C. O’Reilly and J.M. Beck Int. J. Numer. Meth. Engng 2006; 00-1-16] which also conducts stability analysis for solving the wave equation and heat equations using a forward Euler scheme.


\(\nabla^{2}\phi\approx\frac{3}{13h^{2}}\Big(\sum_{j\in\mathcal{N_{f}}} \phi_{j}+\frac{1}{2} \sum_{j\in\mathcal{N}_{e}} \phi_{j}+\frac{1}{3} \sum_{j\in\mathcal{N}_{c}} \phi_{j} – \frac{44}{3}\phi_{i}\Big)\)

Just as an example in C++:


#define n_points 512
#define tot_points n_points*n_points*n_points
#define dx 0.1
#define dt 0.01
#define cec -(44.0/3.0)*(3.0/(13.0*dx*dx)) //center,face,..
#define fac (3.0/(13.0*dx*dx))
#define edc 0.5*(3.0/(13.0*dx*dx))
#define coc (1.0/3.0)*(3.0/(13.0*dx*dx))
#define forxyz(X) for(int x = 1; x &lt; n_points-1; x++) {for(int y = 1; y &lt; n_points-1; y++) {for(int z = 1; z &lt; n_points-1; z++) { X }}}

int c(int x, int y, int z) {
  return n_points*n_points*x+n_points*y+z;
}

void laplacian(std::array&lt;float,tot_points> *arrout, std::array&lt;float,tot_points> *arrin) {
  (*arrout) = {};//1 sec out of 15                                                                                                                                                            
  //9 3 slices                                                                                                                                                                                

  float temp;

   for(int i = -1; i &lt; 2; i++) {
     for(int j = -1; j &lt; 2; j++) {
       int c1;
       int c2;

       if((i==0)and(j==0)) {//face center face                                                                                                                                                
         forxyz(
                c1=c(x+i,y+j,z);
                c2=c(x,y,z);
                (*arrout)[c2]+=fac*(*arrin)[c1-1]+cec*(*arrin)[c1]+fac*(*arrin)[c1+1];
                );

       } else if((abs(i)==1)and(abs(j)==1)) { //corner edge corner                                                                                                                            
         forxyz(
                c1=c(x+i,y+j,z);
                c2=c(x,y,z);
                (*arrout)[c2]+=coc*(*arrin)[c1-1]+edc*(*arrin)[c1]+coc*(*arrin)[c1+1];
                );

       } else {//edge face edge                                                                                                                                                                
   forxyz(
                c1=c(x+i,y+j,z);
    c2=c(x,y,z);
    (*arrout)[c2]+=edc*(*arrin)[c1-1]+fac*(*arrin)[c1]+edc*(*arrin)[c1+1];
    );
       }
     }
   }
  return;
}

To be updated with CUDA, method of relaxation, …

Biofeedback Smartwatch

<Informal because I have no energy, also partially for my own reference later. I’ve been busy when not blogging.>

The PCBs I designed for the Bluetooth biofeedback thing arrived a few days ago. From Oshpark, these were ~10usd total, with free shipping and took ~2 weeks. I’ve never done any QFN soldering (the leads are entirely under the IC) and I didn’t get a solderpaste stencil. Big mistake. Spent a lot of painstaking time with an Exacto knife, sewing needle and tweezers. Also tried using a hot air station for the first time, but a pan on the stove ended up working better.


Small one is meant to be worn on a strap on the finger, the large one is meant to be worn on a strap on the wrist.
SWD pins connected, and it can flash!!!!!!!

Anyway, I got 2 assembled and one of them works with my st-link and openocd. Unfortunately I lost all of my rf inductors during assembly (also the only parts I lost), so I can’t test the bluetooth. I’m using the nRF52810, have downloaded the SDK kit from https://www.nordicsemi.com/Software-and-Tools/Software/nRF5-SDK/Download#infotabs, and can get the examples to compile with the compiler from https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm. I’m mentioning where I got the compiler because I spent a few hours trying to get the gentoo package crossdev working before just downloading binaries.

openocd -f ~/openocd/tcl/interface/stlink.cfg -f ~/openocd/tcl/target/nrf52.cfg -c “program nrf52810_xxaa.bin verify reset exit”

Because it will take time anyway for new inductors to arrive, I did a redesign and ordered more boards. I hope to actually be able to wear the next generation around. I’ll put in some filler posts on computing before then.

For those who stuck with it

Sorting LaTeX references

I don’t use .bib files and just populate bibliographies like:


\begin{thebibliography}{99}

\bibitem{Romanelli1989}
  F.~Romanelli, Phys. Fluids B \textbf{1}, 1018 (1989).

%.... (other citations)

One issue is that this can be out of order compared to the order of /cite{} in the text. The following python script goes through the citations in the text and prints the appropriately ordered bibliography. Replacing the bibliography must be done by hand, but it’s safer that way.


f=open("article.tex","rb")

cites=[]
A=f.tell()

for line in f.readlines():
  while(True):
    a=line.find(r"\cite{")
    if(a==-1):
      break
    else:
      line=line[a+5:]
      b=line.find("}")
      blah=line[1:b]
      if(blah!=""):
        B=blah.split(",")
        for e in B:
          if e not in cites:
            cites.append(e)
      line=line[b:]

refs=[]

f.seek(A)
last_line=""
for line in f.readlines():
  if(last_line[:5]==r"\bibi"):
    refs.append((last_line,line))
  last_line=line

for i in cites:
  for j in refs:
    end=j[0].find("}")                                                                                                                                        
    if(i==j[0][9:end]):
      print j[0][:-1]
      print j[1]
      continue

BlueNRG2

Very much in the spirit of the 90-90 rule, this took much longer than I assumed.

So my goal is to put the galvinic skin response, hand temperature and SPO2 sensors onto a ring connected to a bluetooth ‘watch’, and have some phone app which can stream the data. To play around with a microcontroller which has bluetooth, I got the STEVAL-IDB008V2 dev board, which has a BlueNRG2 chip. Because I’ve only played with the Nucleos so far, I was overoptimistic about linux compatibility. On the plus side, my setup for development has gotten much more comfortable-I’m back to using emacs and makefiles, and I’ve learned how to use st-util to flash and debug and I’ve also gotten my first experience using logic analyzers with sigrok and pulseview.

The issue which held me up was actually getting the programs onto the chip. While the dev board has a JTAG header, the default application disables those pins so it cannot be programmed in that way. The alternative is to flash it with the UART bootloader, which requires the chip be booted into a specific mode and a different pair of pins.This board has a USB, but unlike the nucleos, it is not connected to a ST-Link. All of the software for flashing the chip was windows-only, and I had a lot of trouble getting that working.

Anyway, after much toil I managed to work out a way to reliably flash the chip using the windows software on a VM, but at this point I had already mostly written a bootloader using a nucleo as an intermediary. A warning on the very slim odds a reader will be stuck on the bootloader like I was. As far as I can tell, the bootloader will respond with a positive acknowledge even to invalid addresses, only to fail when receiving the data to flash. To work the last bugs out of the bootloader I was working on, I compared the output of the logic analyzer for each of them flashing the chip with the same data. This revealed that the address bytes I was using were off allowing me to fix the program.

~10usd logic analyzer that’s already worth it
Address bytes and acknowledge in Pulseview
Full writing in pulseview. First is the erase command, followed by blocks of 256 bytes.

EEG 4

I’ve been setting up the optoisolators (H11L1) for SPI. Because they invert the signal, you have to switch the clock polarity, polarity for CS, and take the complement of the data (A->0xFF-A). I used magnet wire for some practice, because it’s supposed to be good for fine SMD stuff.

Attempt 1
Oh gosh spaghetti
Test

I still haven’t programmed the ads1299 over this, there’s some issues I need to work out. All I can say is….

;iern winpoi4ny oi24nPON R;L N;LTJNA;LN IN OPQ3NROHN KL;NF842 H-082948ONX:lhnr0P28 H9-2408NI;NL;n:OJN59-8N8203VCH-4MWV88hirlfhlckh4ovrnmldrjksvjilhvnrotv92534vn hrlnnh-v2hnhihp98H7(Gto4hNP

EEG 3

While I haven’t been active here, I’ve been busy and waiting for parts. So the ADS1299 dev board has arrived and I’ve gotten it working-sort of. I’ve also migrated a bunch of work from using the mbed website to using STM32Cube+SW4ST32.

As a test I hooked the DAC output for the nucleo into the channel one for the ADS1299 (top plot), and programmed it to output a sawtooth wave. The other channels are all various internal things that the IC can be connected to depending on the settings loaded into the registers. For example, the fourth plot down is an internally generated square wave for tests, and the one below that is the internal temperature sensor. I’ve switched also switched to outputting data in a raw format over serial instead of ascii. On the host side this looks like:


import serial
ser=serial.Serial("/dev/ttyACM0",115200)
#....
tmp=ser.read(4*n_channels*sample_batch)
for i in range(sample_batch):
  for j in range(n_channels):
    tmp_data[i,j]=struct.unpack('i',tmp[4*(n_channels*i+j):4*(n_channels*i+j+1)])[0]

Unfortunately, there are more issues. When using actual electrodes (3′ in length), the signal is again dominated by 60Hz. To electrically isolate the board, I’m giving it it’s own battery supply (using 2 18650s in series), and using optocouplers. The optocouplers I have are too slow (see next graph), so I’ve been waiting on faster ones since then.

The signal out of the optocoupler (bottom) never has time to fall between bits (hard to see because multiple traces are overlaid).

I also let the magic blue smoke out of the MAX30102 board after roughly mounting it into a strap with the GSR electrodes and powering it on :(.

Photoplethysmogram 2

The MAX30102 arrived and I spent some time. I’ve also gotten metal disks and will try to make rings with polymer clay to hold the electrodes for GSR on better. As such I’m busy and this will be brief.

The device itself is tiny.

After reading through the datasheets, I wrote something to set registers for SpO2 measurements (IR and red LEDs active, there’s a heart rate mode with only one), and after having bad luck with guessing appropriate settings, eventually found all the recommended settings in the application notes for the demonstration board.

I had a bit of difficulty getting data from it, if just because it’s smart. There’s a 32 sample FIFO circular buffer for the data, which is accessed by repeated reading a single byte register. The read and write pointers are accessible but unnessesary for this, as they both automatically increment. Samples are 2 24 bit numbers (IR and red channel). As is, the program I’m using to read data sets an interrupt enable flag on the MAX30102 to check it for available data and then checks it repeatedly. There is also an almost full interrupt, and I spent some time getting the MAX30102 to trigger the nucleo, but I’m still having trouble reading multiple samples from the ADC.

See https://github.com/garthwhelan/Nucleo-MAX30102, which I will hopefully have enough time to make nice because I plan to use this with hand tempurature and GSR for biofeedback.

Anyway, in the end I got a reasonable trace for reflectance values.

RED and IR reflectance values

Galvinic Skin Response 2

I’ve been back to working on GSR, trying to give it an actual test rather than making sure there’s any response at all.

I’ve switched to make one of the inamp inputs be from the DAC on the nucleo so I can switch that voltage to keep it in range. The DAC output is in orange, the voltage across my fingers is in blue, t is in .1 seconds.

I’ve also used cable organizers to secure the electrodes and switched to gold plated electrodes. I don’t think the shape is optimal for keeping a good contact with the skin, but that’s something else I can play with. These aren’t that bad-I can type and work with them on atleast.

The inamp has an amplification of 100X, and the voltage out from the ADC is missing a factor of 3.3V.

I need to read the Boucsein book again to see if there’s typical filtering that’s used on the output and try and determine if there’s a good test.

In the end, I still don’t have a good test.

ICs

Apparently, everything I want has already been done, and cheaper.

I had assumed this was niche enough not to exist, but there are apparently ICs that solve all my problems. I pride myself on not being the type of autist who wastes his time recreating work without cause, so I’m going to order some to play with.

For pulse oximetry:

https://www.digikey.com/product-detail/en/maxim-integrated/MAX30101EFD-/MAX30101EFD–ND/6123035

For EEG:

https://www.digikey.com/product-detail/en/texas-instruments/ADS1298ECGFE-PDK/296-30132-ND/2353228

Photoplethysmogram part 1

In which I trudge through some simple tests and realize it helps to read a bit before starting things. Today I tried to make a very very simple photoplethysmogram (light-enlargment-gram). Flesh is relatively transparent to red and infrared light, and that changes with the amounts of blood in one’s tissue. This can be used to measure heart rate. The light absorption of hemoglobin changes depending on whether it is attached to oxygen or not. This can be used to measure heart rate too, as well as oxygen saturation (pulse oximetry).

https://en.wikipedia.org/wiki/Photoplethysmogram (PPG)

https://en.wikipedia.org/wiki/Pulse_oximetry

A nice combination of sensors for a wearable watch+ring would be PPG, perifereal temperature and galvinic skin response. I spent a couple of hours trying to get a basic PPG with some IR LEDs and photodiodes. Unfortunately I charged ahead without reading anything and now realize that PPGs are done differently from what I had assumed. Basically, I was just trying to measure IR light reflected (diffusely) by tissue over time.

I bought some 940 nm IR LEDs and photodiodes and made a photointerrupter as a test (read: pointed them at each other and looked at voltages as I put my hand between them). I also played with them as a distance sensor by reflecting the IR off of objects. This was neat.

IR led (which has a neat blue glow on camera) and photodiode, with voltage measured by the oscilloscope. I tried multiple distances and orientations.

Unfortunately there was no obvious change in reflected IR during a heartbeat when they were pointed into my wrist. I moved them much closer, which caused the diode to always be saturated. I then tried it with a shield around the IR led made of tin foil, securing them together with hot glue. The diode was always saturated in this case as well.

I then tried seperating them with a peice of cardboard. This worked and generated a very small signal fluctuating with my heart beat when it was pressed against flesh. I’m not confident that this was PPG as opposed to just changes in distance and pressure.

For PPG beyond wikipedia:

https://www.researchgate.net/publication/224765089_Pulse_Oximeter_Waveform_Photoelectric_Plethysmography

Takeways:

  • For PPG, only 940nm is displayed (as above).
  • Finger tips are the best position (assuming transmission here).
  • Strain gauges have been used to measure heart rate.

For pulse oximetry:

https://www.sciencedirect.com/science/article/pii/S2405959516301205#br000015

Oximetry is done with a pair of LEDs, a red (660 nm) and an IR one (940nm). Hemoglobin with oxygen absorbs more infrared (NOTE: the paper has an error here “more infrared light (660 nm wavelength) and lesser red light (940 nm wavelength) than Hb.” reverses the frequencies compared to the colors).

Stolen from http://www.dnatechindia.com/basic-working-pulse-oximeter-sensor.html

The ratio of the varying to constant intensities of each LED color is calculated, and the ratio of the two ratios is compared to an empirical fitting to give the saturated O2 levels. Measuring this accurately by reflection is hard, the paper reads as a failure (“presents challenges”), but heart rate is easier than oxygen saturation.