Interface logique du picoampèremètre

En suivant scrupuluseument le datasheet du ddc112, j’ai réussi à coder mon TinyFPGA pour faire l’interfaçage logique et envoyer les données à l’ordinateur via UART.

Pour le uart en verilog, j’utilise cet excellent module : https://www.nandland.com/vhdl/modules/module-uart-serial-port-rs232.html

Après queqlues tests infructeux (dont une histoire classique d’ordre des bits : le ddc112 écrit le MSB en premier sur sa pin de donnée, donc il faut remplir le buffer à reculons, en commençant par le MSB également), j’ai réussi à envoyer la charge de test de 13pC (que je lis curieusement à 14pC, mais bon…)

Il y a deux choses qui restent à regarder pour optimiser la lecture : le zéro n’est pas à zéro, mais tourne autour de -0,1pC (1s d’intégration). Il faudra donc faire une calibration pour compenser cet offset. C’est peut-être dû au design de mon PCB, qui laisse fuire ce courant, ou à une erreur Vos dans l’électronique du ddc112. Quoiqu’il en soit, elle est stable, donc une fois compensée, ça devrait aller. Ça reste tout de même de la haute voltige de mesurer des courants aussi minuscules, étant donné que c’est la première fois que je conçois un circuit aussi précis, je reste satisfait de la performance.

Un autre problème consiste en l’alternance binaire entre deux mesures consécutives, qui rajoute une erreur répétable. Voilà ce que ça donne lorsque rien n’est branché en entrée :

input2 :-0.1136pC
input2 :-0.0942pC
input2 :-0.1086pC
input2 :-0.0989pC
input2 :-0.1096pC

Il y a une légère oscillation de 0,02pC, on voit que la mesure peut être plus précise que cette erreur, avec une résolution en-dessous de 0,001pC (1fC !!). Ma compréhension du phénomène, c’est que cette erreur-ci est due à une différence de gain (ou de capacité de l’intégrateur, etc.) entre l’intégrateur A et B. Rappelons brièvement le principe de fonctionnement du DDC112 : afin de pouvoir offrir une mesure qui intègre en continu (sans dead time), le circuit électronique est doublé pour un même channel : pendant que le premier intégrateur intègre la charge, le deuxième est lu et numérisé, une switch alterne entre les deux à chaque nouvelle mesure. Ainsi, la première mesure vient du circuit A, et la deuxième du B, et cela alterne à l’infini. C’est pourquoi une différence de gain entre les deux se traduit par un pattern répétable à toutes les deux mesures.

Heureusement, on peut faire la calibration avec la charge de test de 13pC, et ce, pour tous les ranges utilisés. En ce moment, j’utilise le range maximal, soit 350pC, parce que l’effet est environ 10 fois pire à 50pC (normal me direz-vous!) et que je suis encore bien loin du 20bit théoriquement atteignable pour que cela vaille la peine. Donc cet effet peut aussi être compensé.

Finalement utiliser le port USB du FPGA serait gagnant, pour ne pas avoir recompiler le code verilog à chaque fois que l’on veut changer le range ou la durée d’intégration, et aussi pour faire la calibration. Le Uart reste très utile pour déboguer un code verilog récalcitrant, je garde ça en tête. Mais la communication USB rajoute une couche de complexité. Mon objectif jusqu’à présent était surtout de valider les performances de mon circuit électronique.

J’ai fait des tests avec une photodiode et si mon picoampèremètre dit vrai, je suis capable de mesurer des plages de courant entre 350pC et 0.1pC. Ça teste mes capacités à faire un noir absolu, puisque le signal dépend de la lumière ambiante malgré la boîte métallique et le tape noir, ce qui est génial et indique une sensibilité lumineuse extrême, de l’ordre du nW/cm^2. Ne reste plus qu’à trouver quelque chose d’une aussi faible luminosité à mesurer!

Le code Verilog sur le FPGA:

module picoampmeter (
  input CLK, //16Mhz Clock
  input DDC_DOUT, //Serial output of DDC
  input DDC_NDVA, //Valid data, active low

  output DDC_TEST, //test DDC with charge injection
  output DDC_CONV, //wich side of integrator is converting
  output DDC_CLK, //clk of DDC112 (10MHz nominal)
  output DDC_DCLK, //data clock for transmission
  output DDC_NDX, //enable serial, active low
  output DDC_RA0, //range lsb
  output DDC_RA1, //range
  output DDC_RA2, //range msb

  output USBPU,  // USB pull-up resistor
  output PIN_1 // Serial TX
);
// drive USB pull-up resistor to '0' to disable USB
assign USBPU = 0;

//Uart module
reg enableUartTx; //Enable uart transmitter
reg [7:0] UartByte; //Byte to send trough uart
wire UartDone;
parameter c_CLKS_PER_BIT = 1667; //Baudrate 9600, 16'000'000/9600 = 1667
uart_tx #(.CLKS_PER_BIT(c_CLKS_PER_BIT)) UART_TX_INST
(.i_Clock(CLK),
 .i_Tx_DV(enableUartTx),
 .i_Tx_Byte(UartByte),
 .o_Tx_Active(),
 .o_Tx_Serial(PIN_1),
 .o_Tx_Done(UartDone)
 );

//Select range of integrator
// 001 => 0 to 50pC
assign DDC_RA0 = 1;
assign DDC_RA1 = 1;
assign DDC_RA2 = 1;

//Generate 8MHz clock for DDC CLK
reg clk_8;
always @(posedge CLK) begin
  clk_8 <= !clk_8; //Divide clk by 2
end
assign DDC_CLK = clk_8;

parameter integration_time_us = 15999999;
//Generate integration signal (conv)
reg conv;
reg [23:0] counter = integration_time_us; //1s integration
always @ (posedge CLK) begin
  if (counter == 0)
    begin
      conv <= !conv;
      counter <= integration_time_us;
    end
  else
    counter <= counter - 1;
end
assign DDC_CONV = conv;

//Generate test signal
assign DDC_TEST = 1'b0; //continuous test (13 pC per integration)

//Read serial data
parameter s_IDLE                 = 3'b000;
parameter s_READDDC              = 3'b001;
parameter s_CHECK_READDC_IS_DONE = 3'b010;
parameter s_TRANSFERUART         = 3'b011;
parameter s_WAITUART             = 3'b100;
parameter s_DONE                 = 3'b101;

reg [2:0] StateMachine = 0;
reg dclk;
reg [39:0] buffer;
reg [5:0] bufferpt;
reg r_ndx;
always @ (posedge CLK) begin
  case(StateMachine)
    s_IDLE :
      begin
        r_ndx <= 1'b1; //disable ndx
        dclk <= 1'b0; //disable dclk
        bufferpt <= 39; //start with msb (big-endian)
        if (DDC_NDVA == 1'b0) //Data valid, read serial data output
          begin
            r_ndx <= 1'b0; //Take ndx low to acknoledge data valid
            StateMachine <= s_READDDC;
          end
        else
          StateMachine <= s_IDLE;
      end

    s_READDDC :
      begin

        if (dclk == 1'b1)
          begin
            buffer[bufferpt] <= DDC_DOUT;
            bufferpt <= bufferpt - 1;
          end
        dclk <= !dclk; //toggle clock

        StateMachine <= s_CHECK_READDC_IS_DONE;
      end

    s_CHECK_READDC_IS_DONE :
      begin

        if (bufferpt < 40)
          StateMachine <= s_READDDC;
        else
          begin
            bufferpt <= 0;
            UartByte <= 10; //newline
            enableUartTx <= 1'b1;
            StateMachine <= s_WAITUART;
          end
        // Display Allo! on UART (debug)
        /*bufferpt <= 0;
        buffer[7:0] <= 65;
        buffer[15:8] <= 108;
        buffer[23:16] <= 108;
        buffer[31:24] <= 111;
        buffer[39:32] <= 33;

        // Display 13pC on both channel
        bufferpt <= 0;
        buffer[39:0] <= 40'b0100001010001111010101000010100011110101;
        UartByte <= 10; //newline
        enableUartTx <= 1'b1;
        StateMachine <= s_WAITUART;
        */
      end

    s_WAITUART :
      begin
        if (UartDone == 1'b1)
          StateMachine <= s_TRANSFERUART;
        else
          begin
            enableUartTx <= 1'b0;
            StateMachine <= s_WAITUART;
          end
      end

    s_TRANSFERUART :
      begin
        if (bufferpt < 39)
          begin
            UartByte <= {buffer[bufferpt+7],
                          buffer[bufferpt+6],
                          buffer[bufferpt+5],
                          buffer[bufferpt+4],
                          buffer[bufferpt+3],
                          buffer[bufferpt+2],
                          buffer[bufferpt+1],
                          buffer[bufferpt]};
            bufferpt <= bufferpt + 8;
            enableUartTx <= 1'b1;
            StateMachine <= s_WAITUART;
          end
        else
          begin
            StateMachine <= s_IDLE;
          end
      end

    s_DONE :
      begin
        StateMachine <= s_DONE;
      end

  endcase
  end
  assign DDC_DCLK = dclk;
  assign DDC_NDX = r_ndx;
endmodule // picoampmeter

Le code python pour lire le port série et retransformer les bits en deux channels en nombres float

import serial
ser = serial.Serial('/dev/ttyUSB0', 9600)
fullscale = 350 #50pC pour le scale actuel codé dans le fpga

while True:
    dataByte = ser.readline()
    #print(dataByte)
    if len(dataByte) > 4:
        #print(dataByte)
        data = [int(d) for d in dataByte]
        input1 = float(data[4]*4096 + data[3]*16 + int(data[2]/16) - 4096) / (2**20-1-4096) * fullscale
        input2 = float((data[2]%16)*65536 + data[1]*256 + data[0] - 4096) / (2**20-1-4096) * fullscale
        print("input 1 :{:.4f}pC, input2 :{:.4f}pC".format(input1,input2))

Fourrure kombucha-asclépiade

En lisant l’article de Christine Knobel dans Make: 72, «Growing Leather : Use kombucha to make this versatile vegan textile in your kitchen» (https://youtu.be/Di6HBBw1Esg), il m’est venu l’idée d’essayer la recette et de me mettre — enfin, direz-vous — à la culture du kombucha.

Je méditais vaguement sur le sujet quand j’ai eu une idée folle : et si on y rajoutait de la soie d’asclépiade, juste pour voir? Pour créer un faux cuir ultra-léger avec des poils, quoi de mieux que d’essayer de mélanger kombucha et asclépiade?

L’ampoule dans ma tête s’est allumée lorsque j’ai lu que le SCOBY (la pellicule champignonesque qui se forme à la surface du kombucha lors de la fermentation) était composé à 100% de cellulose, produite par les bactéries lors d’un procédé biochimique extrêmement complexe. Sachant que la soie de l’asclépiade est composée à environ 90% de cellulose, je me suis tout de suite demandé s’il y aurait une compatibilité chimique, si la cellulose du kombucha pourrait «pousser» sur celle de l’asclépiade et ainsi créer une fusion plus parfaite que n’importe quel adhésif ne le pourrait jamais.

La réponse courte, après deux semaines de fermentation et deux semaines de séchage, est oui! Ça fonctionne surprenament bien. J’ai essayé de placer la soie au-dessus, en-desssous et avant que la pellicule ne se forme. La meilleure méthode est de placer la soie avant que la pellicule ne se forme. La soie est hydrofuge et flotte beaucoup, donc peu de surface est véritablement en contact avec le liquide qui génère la pellicule. En la plaçant en-dessous, le poids fait en sorte qu’elle coule un peu plus, mais cela sépare aussi la première pellicule de la deuxième, ce qui fragilise la couche. Si on veut enfermer la soie entre deux couches de cuir de kombucha, vaut mieux le faire en deux étapes : une première fois avec l’asclépiade, et une seconde fois sans, ensuite la fusion s’opérera lorsque l’on réunira les deux feuilles encore gorgées d’eau. En plaçant la soie sur la pellicule déjà créée, elle colle, mais pas au maximum : la pellicule grossit surtout à l’interface entre le solide et le liquide, mais tout de même, cela fonctionne un peu, parce que les bactéries se trouvent un peu partout et cela a pour effet de rendre la pellicule auto-réparatrice tout le temps qu’elle est «vivante», c’est-à-dire gorgée de thé sucré. Le meilleur effet a lieu lorsque la pellicule se crée entièrement autour des soies d’asclépiade en flottaison à la surface du kombucha.

La prochaine expérience sera de l’essayer sur une grande surface, comme décrit dans l’article de Christine Knobel, afin d’avoir une fourrure d’une dimension acceptable pour créer toutes sortes de vêtements. Finalement, l’alignement des soies reste un enjeu, en les laissant pêle-mêle, elles se figent dans le kombucha telles quelles, ce qui est fonctionnel (et potentiellement très chaud), mais moins esthétique que si je trouvais une façon de toutes les aligner dans le même sens. Ce sera un défi en soi — ou en soie! — puisque les fibres sont extrêmement légères, hydrofuges et cassantes, bref, les mêmes problèmes que d’habitude. Les fibres alignées, le résultat donnerait une fourrure extrêmement jolie, douce et chaude.