RISC-Vコア CH32V003J4M6というSOP8パッケージの8pin品でのUART事情とその改善策の紹介です。
J4M6の8ピンパッケージは、PD1/PD4/PD5とUTXとSWIOが共通ピンです。
で、シリアル出力のデフォルト はUTXピンなので、UARTのシリアル出力を無計画にプログラムしてしまうと、次回からの再書き込みができなくなります。
1 2 3 4 5 6 |
スケッチの書き込み中にエラーが発生しました Ready for Remote Connections Info : WCH-LinkE mode:RV version 2.10 Error: WCH-Link failed to connect with riscvchip Error: 1.Make sure the two-line debug interface has been opened. If not, set board to boot mode then use ISP tool to open it Error: 2.Please check your physical link connection |
こんなんエラーが出てボード認識できなくなります。
書き込みのみで、そのまま製品に実装するのなら、次回の書き換えはしないだろうからよいのですが、ファームの開発中となるといろいろ問題です。
ピンが共用されているもんだから、WriterがFlashを消去しようとSWDIOで通信をしようとしても、CH32V003側が他のピンで既に動作してしまっている場合、SWDIOでの通信がもうできなくなっているという状況です。
同じ事象を以下のサイトでも紹介されています。
8PinのRISC-VマイコンCH32V003J4M6にプログラムを書き込めなくなった時の一手段
小ピンマイコンの酷暑(1) CH32V003、8ピンマイコンからのHelloWorld
EEVblog Electronics Community Forum
SWIOピンからシリアル出力が割り当てられてしまっているので、チップとの通信ができなくなってしまっているので以後のFlash消去や再書き込みができないという訳ですね。
これは、UARTなどのシリアルの通信を実装する場合のファーム開発中の作業性に問題になったり、最終製品にはUARTは実装しないが、ファームの開発中にデバッグ用のモニターとしてシリアル出力でターミナルソフトに表示させたいなどの場合に問題になります。
もう、再起込みできなくなってしまったと途方に暮れるのではなく、以下の手法で再度書き込みができるようになります。
まず、プログラムの書き換え手段として2案を紹介します。
その1.Flashを強制消去をしてしまえば再書き込みができます。
前述のリンクでも紹介されていますが、WCH-LinkUtilityでプログラムを全消去してから再度プログラムを書き込むことで再書き込みできるようになります。
WCH-LinkUtility → Target → Clear All Code Flash-By Power off
ですが、書き換えるたびに消去を別のユーティリティソフトで実施するのは、手間だなという面倒くさがり屋さんには、次の手段があります。
その2.プログラムの先頭にdelayを挿入
たとえば、Arduino環境であれば、setupに非Arduino環境であれば、mainの冒頭にdelay(xxx);を設置し、起動直後5秒程度の”待ち”をいれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... void setup() { delay(5000); Serial.begin(9600); ... } ... もしくは ... int main() { delay(5000); Serial.begin(9600); ... } .... |
で、書き込み時(例えば、書き込みボタンの押下のタイミング)に電源を入れなおして再起動させます。この5秒の待ち時間の間でFlash eraceができればOkです。reset端子があればいいのですが、CH32V003J4M6の8ピン品には、リセット端子が無くPower on resetのみなので、書き込みのタイミングと電源入れ直しにコツが要ります。
書き込み治具の基板にモーメンタリーのB接点かC接点のプッシュスイッチを付けて書き込みボタンの押下と同時に電源OFF→ONしてあげdelayの待ち時間中にFlashの書き込みが開始でればOKです。
Flashなので多分ですが、その1.も、その2.も手続きは同じことをやっているだと思いますが、好きな方で実装すればよいかも思います。
まぁ、趣味の範囲なので、何とかやりますが、まじめにICEなどの開発デバッグ環境をそろええるとフツーに実装された機能なんだろうと思います。
で、少ないピンの共用問題あるあるなんだけど、デバッグ用に他の汎用ピンにUARTでモニター出力できるようにできればと思い少し調べてみました。(※Arduino環境限定)
その3.ピンのリマップでピン機能を切り替える
J4M6のデフォルトのピン設定が、SWDIOとUART TXが共用であれば、ピン設定を他のピン配置にしちゃえばいいよねということです。
まず、Arduino環境でのピン設定です。
…\Arduino15\packages\WCH\hardware\ch32v\1.0.4\variants\CH32V00x\CH32V003F4
上記に保存されている、インストールされたPeripheralPins.cを参照します。バージョンは、v1.0.4です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
... //*** UART *** #ifdef UART_MODULE_ENABLED WEAK const PinMap PinMap_UART_TX[] = { {PD_5, USART1, CH_PIN_DATA(CH_MODE_OUTPUT_50MHz, CH_CNF_OUTPUT_AFPP, 0, AFIO_NONE)}, {NC, NP, 0} }; #endif #ifdef UART_MODULE_ENABLED WEAK const PinMap PinMap_UART_RX[] = { {PD_6, USART1, CH_PIN_DATA(CH_MODE_INPUT, CH_CNF_INPUT_PUPD, PULLUP, AFIO_NONE)}, {NC, NP, 0} }; #endif #ifdef UART_MODULE_ENABLED WEAK const PinMap PinMap_UART_RTS[] = { {PC_2, USART1, CH_PIN_DATA(CH_MODE_OUTPUT_50MHz, CH_CNF_OUTPUT_AFPP, 0, AFIO_NONE)}, {NC, NP, 0} }; #endif #ifdef UART_MODULE_ENABLED WEAK const PinMap PinMap_UART_CTS[] = { {PD_3, USART1, CH_PIN_DATA(CH_MODE_INPUT, CH_CNF_INPUT_PUPD, PULLUP, AFIO_NONE)}, {NC, NP, 0} }; #endif ... |
UARTが有効されている場合のUSART1が利用されデフォルトのピン設定が分かります。
TX = PD5, RX = PD6, RTS = PC2, CTS = PD3です。
例えば、Remapレジスタ AFIO_PCFR1を参照すると、USART1_RM1[21:2]を書き換えることでピンマップが変更できそうですが、SOP8 J4M6では、ピンが限定されているのでUSART1_RM1[21:2] = 10しか選べません。
ですが、残念ながらUSART1_RM1[21:2] = 10としても、SWDIOが今度は、RXと共用されてしまうので、いづれにしてもSWDIOが無効化されてしまいます。
でなら、UARTのTXを汎用ピンで実装してしまいましょう。
その4.シリアル出力の実装
シリアルの出力のみ程度なら、サクッと汎用ピンに機能を実装してしまいましょう。
J4M6でなら、例えば、PD6にTXの機能を実装します。
内容としては、ボーレートの設定、汎用ピンの設定、シリアル出力機能の実装です。
※Arduino環境のバグがありますので、delay値は、1/2の値で設定しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
/* Clock settings HSI = 24MHz HSE = NC PLL clock = HSI 24MHz * 2 = 48MHz -> System clock Bugs(Arduino) Must be set a 1/2 time scale to delay function Ex.delay(1000) => 2msec */ // Program v.xxx // Target device : CH32V003J6M4 // Target Frequency : 48MHz // Date : 202x/xx/xx // Author : Takishita of Delivery Toy Hospital // http://wwww.takishita.jp/toy_hospital // /* Pin definition * 1 : Serial TX * 2 : GND * 3 : Reserved * 4 : VCC * 5 : Reserved * 6 : Reserved * 7 : Reserved * 8 : SWDIO * Arduino IDE compile : CH32V00x */ // TX(PD6) 1.--+ 8 SWDIO // GND 2+ + 7 PWM(PC4) // (A0/PA2) 3+ + 6 (SCL)(PC2) // VCC 4+--+ 5 (SDA)(PC1) #include <ch32v00x.h> #define uTx_pin PD6 // Serial tx PD6 // char uTx_buff [100]; // Serial tx buffer const byte uTx_us = 52; // Serial 9600bps = 104us / 2 = 52us void setup() { // PLL setting RCC->CTLR |= RCC_PLLON; // PLL enable while((RCC->CTLR & RCC_PLLRDY) == 0); // Wait PLL clock locked RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b10, 2, 0); // PLL clock source 24MHz * 2 = 48MHz RCC->CFGR0 = bit_replace(RCC->CFGR0, 0b0001, 4, 4); // AHB HCLK prescaler 1/2 (AHB) 48MHz / 2 = 24MHz => TIM1/TIM2/Serial // PD setting RCC->APB2PCENR |= RCC_IOPDEN; // GPIOD enable // PD6 GPIO setting GPIOD->CFGLR = bit_replace(GPIOD->CFGLR, 0b11, 2, 24); //PD6 output mode on 50MHz GPIOD->CFGLR = bit_replace(GPIOD->CFGLR, 0b00, 2, 26); //PD6 with universal(GPIO) push-pull mode. } void loop () { uTx_str("Hello! World.\r\n"); delay(500); // Serial.println("Hello! World."); } // Buff serial output void uTx_str(const char *str) { while (*str) { uTx_byte(*str); str += 1; } } // Byte serial output void uTx_byte(byte ch) { GPIOD->BSHR = (1<<(16+6)); // Start bit // digitalWrite(uTx_pin, LOW); delayMicroseconds(uTx_us); for (byte i=0; i < 8; i++){ // 8 data bit if (((ch >> (i)) & 0x01) == 1 ) { // if ( bitRead(ch,i) == 1 ) { // #define bitRead(value, bit) (((value) >> (bit)) & 0x01) GPIOD->BSHR = (1<<6); // 1 set } else { GPIOD->BSHR = (1<<(16+6)); // 0 set } // digitalWrite(uTx_pin, bitRead(ch,i)); delayMicroseconds(uTx_us); } GPIOD->BSHR = (1<<6); // Stop bit // digitalWrite(uTx_pin, HIGH); delayMicroseconds(uTx_us); } // bit replace uint32_t bit_replace(uint32_t data, uint32_t byte, uint8_t len, uint8_t shift) { uint32_t mask = ~(((1 << len) - 1) << shift); data &= mask; data |= byte << shift; return data; } // The end of file |
Arduino構文を使用するとサクッと完結に記述できますが、非Arduinoとすると、まま構文が増えますね。
最終製品に実装する際には、SWDIOは不要になるので、そのままピンを寝かしておくには資産的にもったいないので、ファームの開発中は、delayで書き換えを凌いでいいと思います。
PWMでモーターやサーボを回したりや簡単な音を鳴らしたり、ADCもあるので外部入力のボタン類も実装できますね