あらきけいすけのメモ帳

あらきけいすけの雑記帳2

C言語の仕様に「グローバル変数」という名前は出てこない

授業の資料作成で調べ物関連のメモ。C99の仕様書 "ISO/IEC 9899:1999 - Programming languages C" を参照していたら「グローバル変数」という語は全く無かった;その代わり "scope" (6.2.1), "linkage" (6.2.2) という語が丁寧に定義されている。いわゆる「グローバル変数」に相当する部分を拾い読みする:

If the declarator or type specifier that declares the identifier appears outside of any block or list of parameters, the identifier has file scope, which
terminates at the end of the translation unit. (6.2.1 Scopes of identifiers #4)

関数の定義のうち関数本体(function body)も波括弧で括られた複文 "block" なので、"any block" で「そのファイルのすべての関数の外」が含意されるようだ。

An identifier declared in different scopes or in the same scope more than once can be made to refer to the same object or function by a process called linkage. (6.2.2 Linkages of identifiers #1)

"linkage" とはメモリ上で同じ場所にあるものを読み(書き)できること。

If the declaration of a file scope identifier for an object or a function contains the storage-class specifier static, the identifier has internal linkage. (6.2.2 Linkages of identifiers #3)

変数のスコープがそのソースコードファイルの全体になる場合に、その変数は "internal linkage" を持つ。

定義の合わせ技を読み取るのは難しい。

 

Raspberry Pi 3 の SPI を用いてA/Dコンバーターからデータを読む python 関数 readadc() の各行の動作

教育用の覚え書き。ラズパイ3で標準のSPIバス(+RPi.GPIOライブラリ)を用いてA/DコンバーターICからセンサ値を読み出す解説記事に頻出する python 関数 readadc() の各行の動作を確認したのでメモを残す。output()でbit1個分のピンへのON/OFF信号の送出, input()でbit1個分のデータ読み出しを実行しているので、呼び出し時の関数の動作時間は多分、一定していない。コメントはA/DコンバーターとしてMCP3208を想定して書いている。

def readadc(adcnum, clockpin, mosipin, misopin, cspin):
    # 宣言しておく import 文
    #   import RPi.GPIO as GPIO
    # 入力(すべてint型)
    #   adcnum:   データを読み出すMCP3208のチャネル番号 0, ..., 7
    #   clockpin: クロック(CLK)信号を出力するラズパイのピン番号
    #   misopin:  Master In Slave Out のラズパイのピン番号
    #   mosipin:  Master Out Slave In のラズパイのピン番号
    #   cspin:    Channel Selector のラズパイのピン番号
    # 戻り値
    #   int 型, 0, 1, 2, ..., 4095: adcnum チャネルのA/D変換値
    #   -1: adcnum が 0, ..., 7 の範囲にない

    # 読み出しチャンネル番号が 0, ..., 7 に無ければ処理しない
    if adcnum > 7 or adcnum < 0:
        return -1

    # CLK への出力を0に初期化しておく
    #   この関数では CLK のパルス生成を GPIO.output(*,1), 
    #   GPIO.output(*,0) の連続呼出で実行している
    #   CLKパルスの立ち下がりのタイミングで1bitの入出力が起こる
    #
    GPIO.output(cspin, GPIO.HIGH)   # I/Oが起きないようにCSを1にする
    GPIO.output(clockpin, GPIO.LOW) # CLK を0に設定しておく
    GPIO.output(cspin, GPIO.LOW)    # I/Oを始めるためにCSを0にする

    # この3行は commandout に MSB IN のデータ(8bitで上位5bitがデータ)を
    # 作成する部分(秋月の MCP3204/3208 のPDFの p.20, Fig. 5-1.)
    #
    # タイミングチャートの3行目の D_IN に書いてある
    #
    #  [Start][SGL/DIFF][D2][D1][D0]
    #     |       |      |   |   |
    #     |       |      読み出しチャンネルの指定
    #     |   0:差動入力
    #     |   1:シングル・エンド
    #  1:スタートビット
    #
    # を作成する
    commandout = adcnum # [ 0][ 0][ 0][ 0][ 0][D2][D1][D0] adcnum
    commandout |= 0x18  # [ 0][ 0][ 0][ 1][ 1][D2][D1][D0] 0x18と論理和
    commandout <<= 3    # [ 1][ 1][D2][D1][D0][ 0][ 0][ 0] 3bitシフト

    # このループは作成した commandout の上位5bitを1bitずつ送り込むループ
    for i in range(5):

        # commandout の最上位1bitが1ならば、MOSIに1を送り込み、
        # さもなければ、MOSIに0を送り込む
        if commandout & 0x80:
            GPIO.output(mosipin, GPIO.HIGH)
        else:
            GPIO.output(mosipin, GPIO.LOW)

        # MOSIに送り込んだら commandout の内容を1bitずらす
        commandout <<= 1

        # CLKに1パルス送り込む
        GPIO.output(clockpin, GPIO.HIGH)
        GPIO.output(clockpin, GPIO.LOW)

    # 出力データ adcout を1bitずつ読み込むループ
    adcout = 0          # データ保存変数 adcout の初期化
    for i in range(13): # 全部で 13 bit 読み込む

        # A/Dコンバーターの CLK に1パルス送り込む
        GPIO.output(clockpin, GPIO.HIGH)
        GPIO.output(clockpin, GPIO.LOW)

        # adcoutを1bit左にずらして、最下位bitに空きを作る
        adcout <<= 1

        # もし MISO に1が入っていたら、adcout の最下位bitに1を書き込む
        if i>0 and GPIO.input(misopin)==GPIO.HIGH:
            adcout |= 0x1

    # データ通信が終わったら CS をHIGHにして通信を止める
    GPIO.output(cspin, GPIO.HIGH)

    # 読み出しデータを呼び出し側に戻す
    return adcout

複数のGPIOピンからのpwm信号出力が欲しいがゆえにpigpioライブラリをインストールしたが、ライブラリ中のSPIの関数pigpio.spi_*()たちの引数の与え方が分からずに*1、このコードをpigpio.read()/write()を使って書き換えた関数を使ってセンサ値を読み出しているのは内緒。

非同次1階線形常微分方程式の初期値問題の解法

授業のための覚書。y, p(x), q(x)xの関数とするときの\displaystyle\frac{dy}{dx}+p(x)\,y=q(x)の解を求める。

 

大学教養程度の薄い教科書では不定積分を使うものしか見かけないのでメモ。

 

まず同次方程式\displaystyle\frac{dy}{dx}+p(x)\,y=0を変数分離法でx=0からx=tまで積分すると、\displaystyle\int_0^t\frac{1}{y}\frac{dy}{dx}dx=-\int_0^tp(x)dxである。ここで部分積分法より左辺の積分変数をyに変数変換すると\displaystyle\int_{y(0)}^{y(t)}\frac{1}{y}dy=-\int_0^tp(x)dxとなる。よって積分の結果は\displaystyle\ln|y(t)|-\ln|y(0)|=-P(t)となる(ここで\displaystyle P(t):=\int_0^tp(x)dxとする)。これより\displaystyle\left|\frac{y(t)}{y(0)}\right|=e^{-P(t)} \Longrightarrow \displaystyle\frac{y(t)}{y(0)}=\pm e^{-P(t)} だが、t=0の条件より負号は棄却され、\displaystyle{y(t)}={y(0)}e^{-P(t)} となる。*1

次に非同次の解だが、まずy(x)e^{P(x)}微分すると\displaystyle\left(y(x)e^{P(x)}\right)' \displaystyle=y'(x)e^{P(x)}+y(x)P'(x)e^{P(x)} \displaystyle=y'(x)e^{P(x)}+y(x)p(x)e^{P(x)} \displaystyle=q(x)e^{P(x)}であるから、この最左辺と最右辺をx=0からx=tまで積分すると、\displaystyle y(t)e^{P(t)}-y(0)e^{P(0)}=Q(t)となる(ここで\displaystyle Q(t):=\int_0^tq(x)e^{P(x)}dxとする)。P(0)=0に留意して整理すると、求める解は次のようになる:\displaystyle y(t)=y(0)e^{-P(t)}+e^{-P(t)}Q(t).

 

初期値問題の解のx=tでの値:\displaystyle y(t)=y(0)e^{-P(t)}+e^{-P(t)}\int_0^tq(x)e^{P(x)}dx, ただし \displaystyle P(t):=\int_0^tp(x)dx

*1:2019.4.19改稿; 旧版:y(t)y(0)から連続につながっているので、y(0)と同符号になるから、左辺は\displaystyle\ln|y(t)|-\ln|y(0)|=\ln\frac{|y(t)|}{|y(0)|}=\ln\frac{y(t)}{y(0)}となる。以上より両辺の\lnをはらって、y(t)=y(0)e^{-P(t)}となる。

e関連の極限の式を避けて対数関数と指数関数を微分する

 授業のための覚書。高校の数学IIIの合成関数の微分の知識を用いる。数学IIIの知識が一通りあれば、論理で押し切れそう。

 対数関数は対数法則 f(xy)=f(x)+f(y) を満たす可微分な関数なので、合成関数の導関数の公式を使い、この両辺を y微分して xf'(xy)=f'(y) となる。これに y=1 を代入して xf'(x)=f'(1) となる。ゆえに \displaystyle f'(x)=\frac{f'(1)}{x} である。ここで f'(1)=1 となる特別な対数関数を「自然対数」と呼び、 \ln x と表記することにすると、自然対数の導関数\displaystyle (\ln x)'=\frac{1}{x} となる(ところで対数の底の値は?)。

 自然対数の逆関数となる指数関数*1y=f^{-1}(x))の導関数\ln y=x の両辺を x について微分することで y'=y と分かる。この指数関数を便宜的に f^{-1}(x)=e^x と表記することにすると、(e^x)'=e^x となる。

 対数法則に x=y=1 を代入して f(1)=0 となること、x\gt0 のとき x^{-1}\gt0 を使って、対数関数の x での値は定積分 \displaystyle \ln x=\int_1^x\frac{dt}{t} で定義される数である。この定義が破綻しないためには \ln x の定義域を x\gt0 にしないといけない。

 対数の底の具体的な値は \displaystyle\int_1^e\frac{dx}{x}=1 を満たす1より大きい数 e数値計算で求めればよい(よい精度で求められるアルゴリズムがあればよいのだが、ボクは知らない)。

 自然対数以外の底の対数関数は\displaystyle f(x)=f'(1)\int_1^x\frac{dt}{t}=f'(1)\ln xで与えらえる。ここでこの対数関数の底をaとするとf(a)=f'(1)\ln a=1となるので、\displaystyle f'(1)=\frac{1}{\ln a}となる。これよりこの対数関数を\log_aと書くと、\displaystyle\log_ax=\frac{\ln x}{\ln a}となる。これは底の変換の式になっている。これより\displaystyle(\log_ax)'=\frac{1}{x\ln a}となる。

*1:f(x)=X, f(y)=Y と置くと、対数法則の式を指数法則の式 f^{-1}(X)f^{-1}(Y)=f^{-1}(X+Y) に書き換えることができる。