2018年2月7日 星期三

技術筆記 MicroPython


印表玩具
   STM32技術

說明


Python是基本程式語言,可直譯,也可編藝執行。而Micro Python就是運行在MCU上的Python 3(github)。MCU上有完整Python詞法分析器,解析器,編譯器。可離線運行,也可透過命令動態互動。非常適合做時間緊迫,運算複雜度高的應用。缺點是,MicroPython本身佔據不少記憶體空間,MCU上記憶體錙銖必較,或許會有排擠效應。

英文說明如下
MicroPython (GRPL-uPython) is a rewrite of the Python 3.4 programming language tailored for STM32 devices. It offers on-chip compiler, virtual machine and runtime to enable the rapid prototyping of applications. Extension modules provide direct access to the peripherals of the STM32 device. Custom components written in C or C++ can easily be integrated.

移植版本


目前MicroPython可運行於類unix系統、PIC16、ARM、esp8266等平台。STM部分,各種型號的支援程度不同。這裡有目前為止支援的MCU列表。詳細說明在這。分列如下:

1)401開發板,CANNON藍芽開發板

2)405,The pyboard reference development board is a small form-factor electronic circuit board based on a STM32F405 running MicroPython on the bare metal. The STM32 peripherals are exposed via a set of Python functions and classes.

3)407開發板

4)429開發板,有方案,沒套件

運作架構


使用Micro Python需要先產生出空的main.c。
類似空的框架中,INIT和PYTHON_C的對應都會被建立好。main函數位於stmhal/main.c  MCU啟動後進入main函數。

在main函數中大概是初始化MCU的時鐘、各種外部IO的setup,然後執行python的垃圾回收器gc_init(), 初始化python對象列表,相當於啟動了python的虛擬機。

後面緊接着啟動了python的REPL,bind到uart,允許使用者通過外部串口來執行python代碼。

f_res = f_stat("main.py", NULL);
// Parse, compile and execute the main script.
pyexec_file("main.py");
// If there's no script ready, just re-exec REPL
while (!usbdbg_script_ready())
{
    // run REPL
    pyexec_friendly_repl();
  牽涉CONSOLE交互作用的部分,從這個連結可以看到
}
if (usbdbg_script_ready())
{
    // parse, compile and execute script
    pyexec_str(usbdbg_get_script());
}

MicroPython安裝與使用


大致了解後,再來看安裝與使用方式。下載並安裝GNU Tools for ARM Embedded Processors。
sudo apt-get install gcc-arm-none-eabi
window系統下載並安裝ST的DfuSe軟件。
下載MicroPython的源碼,micropython-master.zip
展開MicroPython源碼,打開 stmhal\boards\ 目錄


新建一個CANNON目錄,將NUCLEO_F401RE下的文件複製到CANNON目錄下
在Terminal中進入micropython-master\mpy-cross目錄下

輸入編譯:make
進入micropython-master\stmhal目錄下,輸入 make BOARD=CANNON,就可以編譯了。不過這時編譯出的代碼是不能運行的,因為兩個板子的參數不同。

打開 stmhal\boards\CANNON目錄,先修改文件stm32f4xx_hal_conf.h
找到#define HSI_VALUE    ((uint32_t)8000000)
將數字8000000改為16000000,因為小鋼炮使用了16M的外部時鐘
打開文件 mpconfigboard.h
找到#define MICROPY_HW_CLK_PLLM (8)
將數字8改為16
修改#define MICROPY_HW_HAS_SWITCH  (1)
將1改為0,因為小鋼炮上沒有用戶按鍵
修改#define MICROPY_HW_LED1             (pin_A5) // Green LD2 LED on Nucleo
將pin_A5改為pin_B3,因為兩個板子的LED使用不同的GPIO
修改#define MICROPY_HW_LED_ON(pin)      (mp_hal_pin_high(pin))
將 (mp_hal_pin_high(pin))改為 (mp_hal_pin_low(pin)),因為電路驅動電平不同
修改#define MICROPY_HW_LED_OFF(pin)      (mp_hal_pin_low(pin))
將 (mp_hal_pin_low(pin))改為 (mp_hal_pin_high(pin)),因為電路驅動電平不同
添加下面RTC的定義
// The pyboard has a 32kHz crystal for the RTC
#define MICROPY_HW_RTC_USE_LSE      (1)
#define MICROPY_HW_RTC_USE_US       (0)
#define MICROPY_HW_RTC_USE_CALOUT   (1)
添加UART1
#define MICROPY_HW_UART1_TX      (pin_A9)
#define MICROPY_HW_UART1_RX       (pin_10)
修改SPI1的NSS引腳
#define MICROPY_HW_SPI1_NSS      (pin_A4)
打開文件pins.cvs,這裡預定義了GPIO的名稱
修改LED的GPIO為PB3
修改SW的GPIO為PC13

現在可以再次編譯source code。編譯時建議在Linux下編譯,因為速度快很多,在windows下編譯速度很慢,需要等數分鐘。

準備3個短路塊,連接P1,將BOOT0連接到VCC,BOOT1連接到GND。
將開發板用macroUSB線連接到計算機,因為設置了BOOT0/BOOT1,所以上電後會進入DFU模式。在Windows下如果是第一次使用,會提示安裝驅動,驅動程序就在DfuSe軟件的安裝目錄下。使用DfuSe打開編譯後的dfu文件,並下載到開發板。


從開發角度來看,MicroPython因為結合Python和C,IDE部分,KEIL和uVision都找不到可能的編譯方式。似乎得用gcc編譯。

真正使用時,對話型式的開發環境:

1) openmv有提供一個整合環境( QT產生 )  這是個非常有吸引力的方式。

2) 網路上還有uPyCraft ( 通用型 ) 可以對應處理幾種不同的開發板。

使用方式:透過Python語言控制系統


ESP8266有許多範例,OpenMV操作相似。

因為Micropython已經把開發板各個模組都移植完畢,所以可以直接用Python來控制embedded system,根本不需要加入任何C語言與硬體有關語言,可以快速寫出應用系統。

打開一個串口終端軟件,如kitty、xshell、超級終端等,設置波特率為115200,就可以開始玩micropython了。
先試試直接控制LED
import pyb
pyb.LED(1).on()
pyb.LED(1).off()

在試試用GPIO控制LED。
from pyb import Pin
led=Pin.cpu.B3
led.init(Pin.OUT_PP)
led.value(1)
led.value(0)

用PWM控制LED的亮度
from pyb import Pin, Timer
         
tm2=Timer(2, freq=100)
led=tm2.channel(2, Timer.PWM, pin=Pin.cpu.B3, pulse_width=100)
led.pulse_width_percent(100)
led.pulse_width_percent(1)

呼吸燈
# main.py -- put your code here!
from pyb import Timer, Pin

tm2=Timer(2, freq=200)
led=tm2.channel(2, Timer.PWM, pin=Pin.cpu.B3)

# LED breathing lamp
ia = 1
da = 1
def fa(t):
    global ia, da
    if (ia==0)or(ia==100):
        da=100-da
    ia=(ia+da)0
    led.pulse_width_percent(ia)

tm1=Timer(1, freq=100, callback=fa)

stm32f4如何使用SD

如何製作開發板,第一步,以C程式製作LIB


OpenMV為範例。

若有需要純軟體,例如opencv或是未來的dither,核心C程式,會以LIB形式編譯完成,將interface轉為python可以呼叫的項目,讓Python mapping使用。

例如face_eye_detection.py 內
face_cascade = image.HaarCascade("frontalface", stages=25)
img = sensor.snapshot()
objects = img.find_features(face_cascade, threshold=0.5, scale=1.5)
for face in objects:
    img.draw_rectangle(face)


對應的可以使用的LIB在src\omv\py\py_image.c

static const mp_map_elem_t globals_dict_table[] = {
    {MP_OBJ_NEW_QSTR(MP_QSTR_HaarCascade), (mp_obj_t)&py_image_load_cascade_obj},
};

而對應的function是
mp_obj_t py_image_load_cascade(uint n_args, const mp_obj_t *args, mp_map_t *kw_args)
{
  cascade_t cascade; //這是opencv的結構了
  // Return micropython cascade object
  py_cascade_obj_t *o = m_new_obj(py_cascade_obj_t);
  o->base.type = &py_cascade_type;
  o->_cobj = cascade;
  return o;
}

如何製作開發板,第二步,針對不同開發板,架構MicroPython與硬體對應關係


畢竟不是每個硬體都已經被PYTHON包含,要如何讓板子上的裝置讓PYTHON可用。以DAC為例。啟動之後單片機底層的操作通過micropython-master\stmhal\hal\f4目錄下的HAL驅動來完成,那幺在串口敲下的命令又是如何調用底層的驅動的呢?

拿簡單的DAC來説,與之相關的文檔為micropython-master\stmhal\下的dac.h, dac.c, dac.x文檔聲明並定義了MicroPython board DAC類的方法與屬性,通過MP_DEFINE_CONST_FUN_OBJ_KW或MP_DEFINE_CONST_FUN_OBJ_x等註冊給MicroPython的內建對象。比如下面的函數:pyb_dac_write()函數調用STM32 的HAL lib實現硬件的操作,通過MP將此函數註冊為pyb_dac_write_obj

/// \method write(value)
/// Direct access to the DAC output (8 bit only at the moment).
STATIC mp_obj_t pyb_dac_write(mp_obj_t self_in, mp_obj_t val) {
pyb_dac_obj_t *self = self_in;
if (self->state != DAC_STATE_WRITE_SINGLE) {
DAC_ChannelConfTypeDef config;
config.DAC_Trigger = DAC_TRIGGER_NONE;
config.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
HAL_DAC_ConfigChannel(&DAC_Handle, &config, self->dac_channel);
self->state = DAC_STATE_WRITE_SINGLE;
}
// DAC output is always 12-bit at the hardware level, and we provide support
// for multiple bit "resolutions" simply by shifting the input value.
HAL_DAC_SetValue(&DAC_Handle, self->dac_channel, DAC_ALIGN_12B_R,
mp_obj_get_int(val) << (12 - self->bits));
HAL_DAC_Start(&DAC_Handle, self->dac_channel);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(pyb_dac_write_obj, pyb_dac_write);
最後將所有的方法註冊給MicroPython
STATIC const mp_map_elem_t pyb_dac_locals_dict_table[] = {
// instance methods
{ MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&pyb_dac_init_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&pyb_dac_write_obj },
#if defined(TIM6)
{ MP_OBJ_NEW_QSTR(MP_QSTR_noise), (mp_obj_t)&pyb_dac_noise_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_triangle), (mp_obj_t)&pyb_dac_triangle_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_write_timed), (mp_obj_t)&pyb_dac_write_timed_obj },
#endif
// class constants
{ MP_OBJ_NEW_QSTR(MP_QSTR_NORMAL),      MP_OBJ_NEW_SMALL_INT(DMA_NORMAL) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_CIRCULAR),    MP_OBJ_NEW_SMALL_INT(DMA_CIRCULAR) },
};
STATIC MP_DEFINE_CONST_DICT(pyb_dac_locals_dict, pyb_dac_locals_dict_table);
const mp_obj_type_t pyb_dac_type = {
{ &mp_type_type },
.name = MP_QSTR_DAC,
.make_new = pyb_dac_make_new,
.locals_dict = (mp_obj_t)&pyb_dac_locals_dict,
};
該結構體在dac.h中聲明為外部變量:
extern const mp_obj_type_t pyb_dac_type;
#if MICROPY_HW_ENABLE_DAC
{ MP_OBJ_NEW_QSTR(MP_QSTR_DAC), (mp_obj_t)&pyb_dac_type },
#endif
因此如果想添加內建的對象或方法應該遵循如下的步驟與原則:
1. 創建mp對象:
const mp_obj_type_t pyb_led_type = {
{ &mp_type_type },
.name = MP_QSTR_LED,   ///name
.print = led_obj_print,       ///重載的print方法
.make_new = led_obj_make_new, ///構造函數
.locals_dict = (mp_obj_t)&led_locals_dict, ///該對象所擁有的方法字典
};

2. 創建方法字典led_locals_dict:


3. 實現方法
實現上圖中畫橫線的方法以及第一步中的重載的print方法,構造方法等。
4. 將mp對象pyb_led_type 添加到modpyb.c中的pyb_module_globals_table[]全局python對象表裏:{ MP_OBJ_NEW_QSTR(MP_QSTR_DAC), (mp_obj_t)&pyb_dac_type }

使用PYBOARD的範例。

編譯MicroPython
一般Build方式 連結
Docker 參考連結

沒有留言:

張貼留言