UIAPduinoでADC


先日,UIAPduinoでserialとi2cを使ったら,flashの容量オーバーになってしまった. 一方,serialとADCを使った適当なプログラムをコンパイルしたら,9kバイト程度だったので,これなら十分にflashに書き込めた. 少し戸惑ったのがピンの指定で,例えば,analogRead(1)だと,ピン番号が1のA0では無くA1になるようだ. analogRead(A1)などとしても動くのだろうが,内部ではA1は0xc1などと表されているようだ.

ADCを使うプログラムなら,容量が小さくできるch32funやAlexanderManderaさんの環境を使わずにすむのだが,それらではどの位のサイズになるのか知りたくなるものである. しかし,AlexanderManderaさんの環境では,ADCは実装されていない. 仕方が無いので,自分で書いてみた.

以下の内容の~/.arduino15/packages/alexandermandera/hardware/wch/0.0.2/cores/arduino/wiring_analog.cppというファイルを作る.

#include "Arduino.h"
#include "ch32v003fun.h"
#include "wiring_private.h"
static int write_resolution = 8;
static int read_resolution = 10;
void analogWrite(pin_size_t pin, int val){}
void analogWriteResolution(int bits){}
int analogRead(pin_size_t pin)
{
  uint8_t adc2gpio[] = {0,0,2,3,3,3,3,3};
  uint8_t adc2pin[] = {2,1,4,2,3,5,6,4};
  uint8_t gpio = adc2gpio[pin & 0x07];
  GPIO_TypeDef* port = gpioRegister(gpio);
  uint8_t p = adc2pin[pin & 0x07];
  // ADCCLK = 24 MHz => RCC_ADCPRE = 0: divide by 2
  RCC->CFGR0 &= ~(0x1F<<11);
  // Enable GPIO* and ADC
  RCC->APB2PCENR |= (0x04 << gpio) | RCC_APB2Periph_ADC1;
  // P*p is analog input chl n
  port->CFGLR &= ~(0xf<<(4*p));	// CNF = 00: Analog, MODE = 00: Input
  // Reset the ADC to init all regs
  RCC->APB2PRSTR |= RCC_APB2Periph_ADC1;
  RCC->APB2PRSTR &= ~RCC_APB2Periph_ADC1;
  // Set up single conversion on chl n
  ADC1->RSQR1 = 0;
  ADC1->RSQR2 = 0;
  ADC1->RSQR3 = pin;	// 0-9 for 8 ext inputs and two internals	
  // set sampling time for chl n
  ADC1->SAMPTR2 &= ~(ADC_SMP0<<(3*pin));
  ADC1->SAMPTR2 |= 7<<(3*pin);	// 0:7 => 3/9/15/30/43/57/73/241 cycles
  // turn on ADC and set rule group to sw trig
  ADC1->CTLR2 |= ADC_ADON | ADC_EXTSEL;	
  // Reset calibration
  ADC1->CTLR2 |= ADC_RSTCAL;
  while(ADC1->CTLR2 & ADC_RSTCAL);	
  // Calibrate
  ADC1->CTLR2 |= ADC_CAL;
  while(ADC1->CTLR2 & ADC_CAL);
  // start sw conversion (auto clears)
  ADC1->CTLR2 |= ADC_SWSTART;	
  // wait for conversion complete
  while(!(ADC1->STATR & ADC_EOC));
  // get result
  return ADC1->RDATAR;
}
void analogReadResolution(int bits){read_resolution = bits;}
int getAnalogReadResolution(){return read_resolution;}

そして,wiring_digital.cpp中で定義されているgpioRegisterをwiring_private.hに移した. gpioForPinやgpioPinも移しても良い気がするけど,今回は必要無いので,そのままにしておいた. このコードは,ch32funのadc_polled.cを元に作ったものなのだが,動作は確認したが,十分な検討をしていないので,何か不具合はあるかも知れない. ちなみにこの環境では,A1はPA1のことを意味しており,0番ピンという意味になって,analogRead(A1)とすると,A0の値が読まれるというようなことになるだろうから,A1などの表記は使わない方が良いだろう. また,pinに7よりも大きい数字を指定した場合には,変な動作になる可能性があったので,pin&0x07としておいた. pin%9などの方が良いかも.

先と同じコードをコンパイルしたら,2.4kバイトになった. やはり,1/4程度である. UIAPduinoを使っていると,この環境は有用に思えてくるのだが,以前も書いたように,ADCやPWMやSPI が使えず,i2cはWireでない手法でのみ使える. 今回,ADCが一応使えるようになったので,欠点は少し減ったかも知れないが,まだまだ改良の余地がある. 自分が新たな機能を使う度に,書き足して行けばいいのかな. Arduinoとch32funに詳しい人が,参入してくれないかな.