RP2040 PIOを用いたPICマイコンアクセス
こんにちはリゲル・インテリジェンスです。
PICマイコン(PIC16F1823)へプログラムを書込むには、基本的に4本の信号線をコントロールする必要があります。
- VPP:PicWriterUSBではHigh Voltage Programmingを使用しますので、書込み時に9Vを印加します
- VDD:3.3V電源ですがProgrammingモードに入れるために、VPPと併せてON/OFFのコントロールが必要です
- ICSPCLK:データ読み書きのための同期クロックで、アクセスタイミングの調整も行います(各種Wait付加)
- ICSPDAT:上記クロックに同期してデータを入出力する、唯一の双方向信号線です
このうち、VPPとVDDはプログラミングモードの開始/終了の際に一度だけ操作すれば良い=タイミングに多少余裕をもたせても処理全体の時間には大きな影響は無いためGPIOに割り当てても問題は無いでしょう。一方でICSPCLKとICSPDATはデータの読み書きで1bitずつ処理していくため、仕様に合う範囲で可能な限り早く動作させたいです。PIC16F1823のICSPCLKサイクルは200nsecと高速ですし、要所要所でWaitを入れる=ICSPCLKを動かさない期間を作る必要もあります。この用途にはGPIO+ソフト処理ではなく、RP2040のPIO(Programmable Input/Output)モジュールが非常に良くマッチします。
RP2040のPIOはCPUの直接操作無しに、
- FIFOに積まれたデータを決められたシーケンスでGPIOピンから出力する
- GPIOピンへの入力を決められたシーケンスでFIFOに積む
を行います。この「決められたシーケンス」をアセンブラに似たプログラム(機能が少ないのでスーパーサブセット版といった感じです)にてステートマシンとして記述できます。ステップ数が32迄などあまり複雑な処理は出来ませんが、システムクロックのスピード(125MHzでは1ステップ8nsec)で動かせるので高速な処理にも対応出来ます。
※RP2040 Datasheetより抜粋
下記が今回作成したICSPCLK/ICSPDATコントロールのPIOプログラムです。
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 |
; ; pic_command.pio : PIC micro-contoller access sequence using PIO of RP2040. ; ; Sep12,2022 : start coding. ; ; ; M.Mato ; ; pin mappping: P7=ICSPCLK, P6=ICSPDAT, P3=VPP(/MCLR), VDD control=P26 ; * VPP and VDD control are connected via external hardware ; * only ICSPCLK, ICSPDAT are controlled with this PIO program. ; .program pic_command .side_set 1 ;icspclk_pin .wrap_target start: pull block side 0 set x, 5 side 0 lp_cmd: out pins, 1 side 1 jmp x-- lp_cmd side 0 out x, 1 side 0 ; get command type (1st bit) jmp !x cmd_only side 0 ; command type only out x, 1 side 0 ; get command type (2nd bit) jmp !x cmd_read side 0 ; command type read cmd_load: set pindirs, 1 side 0 set x, 15 side 0 [3] ; TDLY=1usec lp_ld_dat: out pins, 1 side 1 jmp x-- lp_ld_dat side 0 jmp start side 0 [6] cmd_read: set pins, 0 side 0 set pindirs, 0 side 0 set x, 14 side 0 [2] ; TDLY=1usec nop side 1 ; ICSPCLK set as 'H' manually lp_rd_dat: in pins, 1 side 0 ; data read with down edge jmp x-- lp_rd_dat side 1 set pindirs, 1 side 0 set pins, 0 side 0 push block side 0 jmp start side 0 [4] cmd_only: jmp start side 0 [4] .wrap % c-sdk { // this is a raw helper function for setting up the GPIO output/input, // and configures the StateMachine(SM) to output/input on particular pins. void pic_command_init(PIO pio, uint sm, uint offset, uint icspclk_pin, uint icspdat_pin) { pio_sm_config sm_config = pic_command_program_get_default_config(offset); pio_gpio_init(pio, icspclk_pin); pio_gpio_init(pio, icspdat_pin); sm_config_set_out_pins(&sm_config, icspdat_pin, 1); sm_config_set_set_pins (&sm_config, icspdat_pin, 1); sm_config_set_in_pins (&sm_config, icspdat_pin); sm_config_set_sideset_pins(&sm_config, icspclk_pin); sm_config_set_out_shift(&sm_config, true, false, 24); sm_config_set_in_shift (&sm_config, true, false, 16); sm_config_set_clkdiv(&sm_config, 13); // 8nsec(125MHz) *13 = 104nsec /inst. uint pin_mask = (1u << icspclk_pin) | (1u << icspdat_pin); pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); pio_sm_init(pio, sm, offset, &sm_config); pio_sm_set_enabled(pio, sm, true); } %} |
main()とCMakeLists.txtです。
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
//****************************************************************************** //* pio_test.c : test program to access PIC16F1823 via RP2040 PIO module. * //* * //* usage: * //* * //* history: * //* Sep12,2022 start coding. N.Mato * //* * //* Copyright(c)2022 Rigel Intelligence * //****************************************************************************** //*** includes ***************************************************************** #include <stdio.h> #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "pic_command.pio.h" // generated automatically in build folder //*** definitions ************************************************************** // clock unit 100nsec for PIC programming (125MHz,13clocks=104nsec) #define UNIT_100NSEC ((clock_get_hz(clk_sys)/10000000)+1) // port definitions #define LedPinBlue 25 // output: the number of the LED pin of Blue #define LedPinGreen 16 // output: the number of the LED pin of Green #define LedPinRed 17 // output: the number of the LED pin of Red #define VPP_MCLR 3 // output: P3 for controll PIC VPP/MCLR pin #define VDD_CONTROL 26 // output: P26 for controll PIC VDD #define ICSPCLK 7 // output: P7 for controll PIC ICSPCLK #define ICSPDAT 6 // output/input: P6 data out/in of PIC ICSPDAT // PIO state machine #define SM_PIC_COMMAND 0 // PIC command executing state machine, fixed 0 // PIC programming mode #define PGMODE_ENTER 0 // enter PIC programming mode #define PGMODE_EXIT 1 // exit PIC programming mode // PIC command type #define CMD_LOAD 0xC0 // command "write" data to PIC #define CMD_READ 0x40 // command "read" data from PIC #define CMD_ONLY 0x0 // command to PIC without data #define DATA_SHIFT 9 // 9-bits shift to pickup 14bit data // PIC access commands #define LOAD_CONFIGURATION 0x0 // 0, dataW(14),0 #define LOAD_DATA_FOR_PROGRAM_MEMORY 0x2 // 0, dataW(14),0 #define LOAD_DATA_FOR_DATA_MEMORY 0x3 // 0, dataW(8),zeroW(6), 0 #define READ_DATA_FROM_PROGRAM_MEMORY 0x4 // 0, dataR(14),0 #define READ_DATA_FROM_DATA_MEMORY 0x5 // 0, dataR(8),zeroR(6), 0 #define INCREMENT_ADDRESS 0x6 // no data #define RESET_ADDRESS 0x16 // no data #define BEGIN_INTERNALLY_TIMED_PROGRAMMING 0x8 // no data #define BEGIN_EXTERNALLY_TIMED_PROGRAMMING 0x18 // no data #define END_EXTERNALLY_TIMED_PROGRAMMING 0xA // no data #define BULK_ERASE_PROGRAM_MEMORY 0x9 // no data (internally timed) #define BULK_ERASE_DATA_MEMORY 0xB // no data (internally timed) #define ROW_ERASE_PROGRAM_MEMORY 0x11 // no data (internally timed) enum ACCESS_TIMING { // device access timing (x 1usec unit, except clock_cycle) CLOCK_CYCLE = 200, // ICSPCLK clock cycle (200nsec) TDLY = 1, // TDLY : delay between command and data (1usec) TPINT = 5000, // TPINT: wait for internally timed programming (5msec) TPEXT = 1000, // TPEXT: wait for externally timed programming (1~2.1msec) TDIS = 500, // TDIS : additional delay for next command (100usec~ ??) TERAB = 5000, // TERAB: wait for bulk erase (5msec) TERAR = 2000, // TERAR: wait for row erase (2msec) TENTS = 1, // TENTS: setup time to enter programming mode (minimum 100nsec) TENTH = 250, // TENTH: hold time to enter programming mode (250usec) TEXIT = 1 // TEXIT: delay for exiting programming mode (1usec) }; //*** variables **************************************************************** static PIO pio = pio0; // pio0 or pio1 can be used static uint32_t cmd_buffer; // command buffer structure (send) // MSB--------------------------------LSB // fedcba98 7 6543210fedcba9 8 76 543210 // n/a |0| data |0|ty|command // (all 0) |0| 14bits |0|2b| 6bits // * "ty" indicates command type, not specified in PIC. // This is internal use only without ICSPCLK. // * return(read) data is set 14bit on LSB simply //****************************************************************************** //*** functions **************************************************************** //****************************************************************************** void pic_access_init( void ) { // VPP_MCLR, VDD_CONTROL are set as GPIO gpio_init(VPP_MCLR); gpio_set_dir(VPP_MCLR, GPIO_OUT); gpio_put(VPP_MCLR, 0); // set as low gpio_init(VDD_CONTROL); gpio_set_dir(VDD_CONTROL, GPIO_OUT); gpio_put(VDD_CONTROL, 0); // set as low, on PIC as 'VDD=OFF' uint offsetCommand = pio_add_program(pio, &pic_command_program); printf("Loaded pic command at %d\n", offsetCommand); pic_command_init(pio, SM_PIC_COMMAND, offsetCommand, ICSPCLK, ICSPDAT); printf("System clock %d, UNIT_100NSEC %d, ", clock_get_hz(clk_sys), UNIT_100NSEC ); // wait a bit for initialization sleep_ms(10); } void pic_programming_mode( bool mode ) { if( mode ) { // enter programming mode with 'VDD–FIRST ENTRY MODE', Vpp is set as 9V // VDD 1st mode, but due to hardware issue, need to wait 1msec. gpio_put(VDD_CONTROL, 1); // Turn on Vdd sleep_us(TENTS + 1000); // TENTS: minimum 100nsec + additional wait 1msec gpio_put(VPP_MCLR, 1); // Turn on Vpp/MCLR as 9V sleep_us(TENTH); // TENTH: minimum 250usec } else { // exit programming mode with 'VDD–FIRST ENTRY MODE', Vpp is set as 9V // VDD 1st mode, but due to hardware issue, need to wait 100msec. gpio_put(VPP_MCLR, 0); // Turn off Vpp/MCLR as 9V sleep_us(TEXIT); // TEXIT: minimum 1usec gpio_put(VDD_CONTROL, 0); // Turn off Vdd sleep_ms(100); // additional wait } } //*** PIO access low level functions ******************************************* static void pic_command_load( PIO pio, uint sm, uint command, uint32_t *data ) { uint32_t cmd_buf = *data; cmd_buf <<= DATA_SHIFT; cmd_buf += (CMD_LOAD + command); pio_sm_put_blocking( pio, sm, cmd_buf ); } static void pic_command_read( PIO pio, uint sm, uint command, uint32_t *data ) { uint32_t cmd_buf; cmd_buf = (CMD_READ + command); pio_sm_put_blocking( pio, sm, cmd_buf ); *data = (pio_sm_get_blocking(pio, sm) >>18); } static void pic_command_only( PIO pio, uint sm, uint command ) { uint32_t cmd_buf; cmd_buf = (CMD_ONLY + command); pio_sm_put_blocking( pio, sm, cmd_buf ); } //*** main ********************************************************************* int main( void ) { // GPIO initialization (LEDs on XIAO-RP2040 board) gpio_init(LedPinGreen); gpio_set_dir(LedPinGreen, GPIO_OUT); gpio_put(LedPinGreen, 1); // OFF gpio_init(LedPinRed); gpio_set_dir(LedPinRed, GPIO_OUT); gpio_put(LedPinRed, 1); // OFF gpio_init(LedPinBlue); gpio_set_dir(LedPinBlue, GPIO_OUT); gpio_put(LedPinBlue, 0); // ON // for log message stdio_init_all(); // initialize PIO. pic_access_init(); // enter programming mode, VDD-1st mode pic_programming_mode( true ); // send command: LOAD CONFIGURATION cmd_buffer = 0x3FFF; // dummy pic_command_load( pio, SM_PIC_COMMAND, LOAD_CONFIGURATION, &cmd_buffer ); // address move to 8006h = Deive ID pic_command_only( pio, SM_PIC_COMMAND, INCREMENT_ADDRESS ); pic_command_only( pio, SM_PIC_COMMAND, INCREMENT_ADDRESS ); pic_command_only( pio, SM_PIC_COMMAND, INCREMENT_ADDRESS ); pic_command_only( pio, SM_PIC_COMMAND, INCREMENT_ADDRESS ); pic_command_only( pio, SM_PIC_COMMAND, INCREMENT_ADDRESS ); pic_command_only( pio, SM_PIC_COMMAND, INCREMENT_ADDRESS ); // read configuration memory pic_command_read( pio, SM_PIC_COMMAND, READ_DATA_FROM_PROGRAM_MEMORY, &cmd_buffer ); printf("Device ID: %04Xh\n", cmd_buffer & 0x3fff ); // enter programming mode, VDD-last mode pic_programming_mode( false ); while(1) { sleep_ms(100); } } |
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 |
cmake_minimum_required(VERSION 3.13) # Pull in SDK (must be before project) include(../pico_sdk_import.cmake) # project name set(PROJECT "pio_test") project(${PROJECT} C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) pico_sdk_init() # executable name add_executable(${PROJECT} src/pio_test.c ) # source code target_sources(${PROJECT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/pio_test.c ${CMAKE_CURRENT_SOURCE_DIR}/src/pic_command.pio ) # include path target_include_directories(${PROJECT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src ) # pioasm generation pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/src/pic_command.pio) # enable serial output pico_enable_stdio_usb(${PROJECT} 1) pico_enable_stdio_uart(${PROJECT} 1) # pull in common dependencies target_link_libraries(${PROJECT} pico_stdlib hardware_pio) # create map/bin/hex file etc. pico_add_extra_outputs(${PROJECT}) |
これらを下記のフォルダに配置し、いつものようにビルドします。
1 2 3 4 5 6 7 |
work/ ├── pico_sdk_import.cmake └── pio_test/ ├── CMakeLists.txt └── src/ ├── pio_test.c └── pic_command.pio |
pio_test.uf2ファイルをRP2040に転送し、PIC16F1823を接続した状態でプログラムを走らせると下記のログが得られます。
正しくDevice IDが読めています。次ページにてPIOプログラムの内容をもう少し詳しく見ていきます。
コメント