2024年2月19日 星期一

STM32F103 的 timer 的使用

目的

在改裝 3D 印表機時,需要在有限的空間內加上一些調整時會用的裝置,只要把它安裝在可動的座上。參考網路的作法,使用舵機 MG90S 來改變位置。MG90S 是使用 50Hz 的方波的 duty cycle 寬度來控制舵的角度。

為了瞭解 MG90S 的運作方式,使用 STM32F103 的開發板來控制它,程式語言則是使用 Forth。Forth 兼具 interactive 和 compile 的特性,可以在 terminal 直接發送指令,也可將一堆指令包成一個 word (等同於其他程式言的 function),然後在 terminal 直接測試新的 word。並且有能夠直接存取特定 IO 的低階指令,在開發硬體上很方便。

參考 

STM32 的 PWM 原理

STM32 有不同編號的 MCU,功能上差不多,但控制上稍微不同,必須找對應的 Reference manual 來看,如 STM32F101xx 要看 RM0008 的文件,STM32F407/417 則要看 RM0090 的文件。

一張圖勝於長篇解說,下面的圖借自網站 deepbluembedded 的圖。



STM32F103 微控制器采用 Cortex-M3 内核,CPU 最高速度达 72 MHz。MG90S 控制信號的頻率為 50Hz。

以下參考 embeddedexpert 的關於 timer 使用的說明。

1. stm32 的 timer 的工作簡要說明:

A Timer Module in its most basic form is a digital logic circuit that counts up every clock cycle. More functionalities are implemented in hardware to support the timer module so it can count up or down. It can have a Prescaler to divide the input clock frequency by a selectable value. It can also have circuitry for input capture, PWM signal generation, and much more as we’ll see in this tutorial.

Let’s consider a basic 16-Bit timer like the one shown below. As a 16-Bit time, it can count from 0 up to 65535. Every clock cycle, the value of the timer is incremented by 1. And as you can see, the Fsys is not the frequency that is incrementing the timer module. But it gets divided by the Prescaler, then it gets fed to the timer.

Basically, in timer mode, the TCNT register is incremented by 1 each clock cycle @ the following frequency (Fsys/PSC). This means if the Fsys is 80MHz & PSC is 1:1024, the TCNT gets incremented by 1 every 12.8μSec. Therefore, if you start this timer to count from 0 until it reaches overflow (at 65535), a flag will be set and starts over from zero.

2. 設定延遲時間:

Since STM32F103 has multiple timers, we shall use timer2 for this guide.

First, we need to enable clock access to TIM2 of STM32F1. In order to find which bus is connected to, we need the block diagram of STM32F103 which can be found in datasheet of STM32F1.

From the block diagram, we can find that TIM2 is connected to APB1 bus:


Hence, to enable clock access to TIM2:
/*Enable clock access to timer2*/
RCC->APB1ENR|=RCC_APB1ENR_TIM2EN;

Now, we can create a function named delay which takes ms as argument to indicate the amount to be delayed as following:

void delay(uint16_t ms)

Within the function, set the prescaller for the timer, since STM32F103C8 by default runs at 8MHz, we shall use prescaller of 8000-1 as following:

TIM2->PSC=8000-1; //8 000 000 Hz / 8 000 = 1 000 Hz (1 ms) 

Set the Auto reload register the desired delay -1 :

TIM2->ARR=ms-1;   // desired delay

Enable the timer from control register 1 (CR1):

TIM2->CR1|=TIM_CR1_CEN; 

Wait until the update flag is set in status register and then clear it once it is set:

while(!(TIM2->SR&TIM_SR_UIF)){} //wait UIF to be set
TIM2->SR&=~TIM_SR_UIF; //reset UIF 

Finally, disable the timer:

TIM2->CR1&=~TIM_CR1_CEN;

3. 全部的程式碼:

Hence, the entire code as following::

#include "stm32f1xx.h"

void delay(uint16_t ms)
{
	TIM2->PSC=8000-1; //8 000 000 Hz / 8 000 = 1 000 Hz (1 ms)
	TIM2->ARR=ms-1;   // desired delay
	TIM2->CR1|=TIM_CR1_CEN;
	while(!(TIM2->SR&TIM_SR_UIF)){} //wait UIF to be set
	TIM2->SR&=~TIM_SR_UIF; //reset UIF
	TIM2->CR1&=~TIM_CR1_CEN; // Disable the timer
}

int main(void)
{
	/*Enable clock access to GPIOA*/
	RCC->APB2ENR|=RCC_APB2ENR_IOPAEN;

	/*Configure PA0 as output*/
	GPIOA->CRL|=GPIO_CRL_MODE0;
	GPIOA->CRL&=~(GPIO_CRL_CNF0);
  
	/*Enable clock access to timer2*/
	RCC->APB1ENR|=RCC_APB1ENR_TIM2EN;

	while(1)
	{

		GPIOA->BSRR=GPIO_BSRR_BS0;//Set PA0 to high
		delay(1000); //Delay for 1 seconds
		GPIOA->BSRR=GPIO_BSRR_BR0; // Set PA0 to low
		delay(1000); // Delay for 1 seconds

	}
}


Forth 程式

依據上面的說明,以及參考其他的 Forth 程式,撰寫的 PWM 測試程式如下

\ STM32f103 PWM functiontest
\ 由 GPIO 可用的 alternate function 決定使用的 timer 和 channel
\ PA1 -- USART2_RTS / ADC12_IN1 / TIM2_CH2

\ 8000000 constant HCLK
72 1000 1000 * *  constant HCLK  \ 72 MHz ?
\ 80 1000 1000 * *  constant HCLK 

$40021000 constant RCC_BASE    
  RCC_BASE $18 + constant RCC_APB2ENR   
  RCC_BASE $1C + constant RCC_APB1ENR   

4 constant GPIOAEN 
1 constant TIM2EN

$40010800 constant GPIOA  
    GPIOA $0 + constant GPIOA_CRL
    GPIOA $4 + constant GPIOA_CRH
    GPIOA $8 + constant GPIOA_IDR
    GPIOA $C + constant GPIOA_ODR
    GPIOA $10 + constant GPIOA_BSRR
    GPIOA $14 + constant GPIOA_BRR
    GPIOA $18 + constant GPIOA_LCKR

$40000000 constant TIM2 ( Advanced timer ) 
TIM2 $0 + constant TIM2_CR1 ( read-write )  \ control register 1
TIM2 $4 + constant TIM2_CR2 ( read-write )  \ control register 2
TIM2 $8 + constant TIM2_SMCR ( read-write )  \ slave mode control register
TIM2 $C + constant TIM2_DIER ( read-write )  \ DMA/Interrupt enable register
TIM2 $10 + constant TIM2_SR ( read-write )  \ status register
TIM2 $14 + constant TIM2_EGR ( write-only )  \ event generation register
TIM2 $18 + constant TIM2_CCMR1 ( read-write )  \ capture/compare mode register output  mode
TIM2 $1C + constant TIM2_CCMR2 ( read-write )  \ capture/compare mode register output  mode
TIM2 $20 + constant TIM2_CCER ( read-write )  \ capture/compare enable  register
TIM2 $24 + constant TIM2_CNT ( read-write )  \ counter
TIM2 $28 + constant TIM2_PSC ( read-write )  \ prescaler
TIM2 $2C + constant TIM2_ARR ( read-write )   \ auto-reload register
TIM2 $34 + constant TIM2_CCR1 ( read-write )  \ capture/compare register 1
TIM2 $38 + constant TIM2_CCR2 ( read-write )  \ capture/compare register 2
TIM2 $3C + constant TIM2_CCR3 ( read-write )  \ capture/compare register 3
TIM2 $40 + constant TIM2_CCR4 ( read-write )  \ capture/compare register 4
TIM2 $48 + constant TIM2_DCR ( read-write )  \ DMA control register
TIM2 $4C + constant TIM2_DMAR ( read-write )  \ DMA address for full transfer
TIM2 $30 + constant TIM2_RCR ( read-write )  \ repetition counter register
TIM2 $44 + constant TIM2_BDTR ( read-write )  \ break and dead-time register

1 7 lshift constant ARPE 			\ Auto-reload preload enable
1 constant CEN								\ Counter enable
1 3 lshift constant OPM	 			\ One-pulse mode
1 constant UG		              \ Update generation

\ GPIOA_CRL - only for PA0 - PA7
: gpioa-mode! ( mode pin# -- )
  4 * dup
  $F swap lshift not GPIOA_CRL @ and
  -rot lshift or GPIOA_CRL !
;

: set-pwm ( period dutycyle -- )
	1- TIM2_CCR2 !	  \ duty cycle
	1- TIM2_ARR !	  \ period
;

: s-psc 
  1- TIM2_PSC !	
;

: p-psc 
  TIM2_PSC @ 1+ .	
;

: s-arr
	1- TIM2_ARR !	  \ period
;

: p-arr
	TIM2_ARR @ 1+ .	  \ period
;

: s-ccr 
  1- TIM2_CCR2 !	
;

: p-ccr 
   TIM2_CCR2 @ 1+ .
;

\  --- init output PA1
: init-PA1 ( -- )
  \ IO port A clock enabled
  GPIOAEN RCC_APB2ENR bis!

  \ PA1 -> mode: alternate -> set value 1010 
  \ CNF  - 10 -> 1: Alternate Function, output, 0: Push-pull
  \ MODE - 10: Maximum output speed 2 MHz
  %1010 1 gpioa-mode!
;

  \ 110: PWM mode 1 - In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1
  \ else inactive. In downcounting, channel 1 is inactive (OC1REF=‘0) as long as
  \ TIMx_CNT>TIMx_CCR1 else active (OC1REF=1).

\ --- init TIM2_CH2
: init-tim2_ch2 ( -- )
	TIM2EN RCC_APB1ENR bis!			\ TIM2 clock enabled
	ARPE TIM2_CR1 ! 			\ ch2 Auto-reload preload enable

  \ Bit 5 CC2P: Capture/Compare 2 output polarity, 0: OC2 active high.
  \ Bit 4 CC2E: Capture/Compare 2 output enable
	1 4 lshift TIM2_CCER ! 				\ CC2E -> OC2 signal is output on the corresponding output pin
	1 11 lshift TIM2_CCMR1 bis!			\ OC2PE (bit 11) -> Output compare 2 preload enable

	%110 12 lshift TIM2_CCMR1 bis!	\ Output compare 2 mode -> 110: PWM mode 1

  \ Update generation - Reinitialize the counter and generates an update of the registers.
	UG  TIM2_EGR !	
;
	
: pwm-init ( -- )
  init-PA1
  init-tim2_ch2

  \ HCLK 是 72MHz,prescaler 不能超過 65536
  \ 所以只能設到 2KHz
  HCLK 2000 / 1- TIM2_PSC !		\ prescaler -> 2000 Hz = 0.5 ms
  2000 50 set-pwm			\ 1000ms - 50ms period - dutycycle as initial value

  \ counter enable
	CEN TIM2_CR1 bis! 
;



舵機控制



MG90S - (S: 金屬齒輪),模拟舵机,180度 - 才能控制角度,360 度只能旋轉
数字舵机Digital Servo和模拟舵机Analog Servo

PWM 測試

控制信號頻率 50Hz,週期 20ms


8MHz = 8*1000*1000


prescaler: 1000 => 8*1000



prescaler for 1us => 1MHz



-----------

   舵機是一種位置(角度)伺服的驅動器,適用於那些需要角度不斷變化並可以保持的控制系統。目前在高檔遙控玩具,如航模,包括飛機模型,潛艇模型;遙控機器人中已經使用得比較普遍。舵機是一種俗稱,其實是一種伺服馬達。


其工作原理是:


控制信號由接收機的通道進入信號調製晶片,獲得直流偏置電壓。它內部有一個基準電路,產生週期為20ms,寬度為1.5ms的基準信號,將獲得的直流偏置電壓與電位器的電壓比較,獲得電壓差輸出。最後,電壓差的正負輸出到電機驅動晶片決定電機的正反轉。當電機轉速一定時,通過級聯減速齒輪帶動電位器旋轉,使得電壓差為0,電機停止轉動。當然我們可以不用去瞭解它的具體工作原理,知道它的控制原理就夠了。就象我們使用電晶體一樣,知道可以拿它來做開關管或放大管就行了,至於管內的電子具體怎麼流動是可以完全不用去考慮的。


舵機的控制:


舵機的控制一般需要一個20ms左右的時基脈衝,該脈衝的高電平部分一般為0.5ms~2.5ms範圍內的角度控制脈衝部分。以180度角度伺服為例,那麼對應的控制關係是這樣的:


   0.5ms--------------0度;

   1.0ms------------45度;

   1.5ms------------90度;

   2.0ms-----------135度;

   2.5ms-----------180度;


這只是一種參考數值,具體的參數,請參見舵機的技術參數。


   小型舵機的工作電壓一般為4.8V或6V,轉速也不是很快,一般為0.22/60度或0.18/60度,所以假如你更改角度控制脈衝的寬度太快時,舵機可能反應不過來。如果需要更快速的反應,就需要更高的轉速了。


要精確的控制舵機,其實沒有那麼容易,很多舵機的位置等級有1024個,那麼,如果舵機的有效角度範圍為180度的話,其控制的角度精度是可以達到180/1024度約0.18度了,從時間上看其實要求的脈寬控制精度為2000/1024us約2us。如果你拿了個舵機,連控制精度為1度都達不到的話,而且還看到舵機在發抖。在這種情況下,只要舵機的電壓沒有抖動,那抖動的就是你的控制脈衝了。而這個脈衝為什麼會抖動呢?當然和你選用的脈衝發生器有關了。


------------


0.5ms - 0 度

14.7ms - 90度

24.5ms - 180度 


stm32f103

較佳的設定

psc - 7200 --> 10KHz -- 0.1ms

arr - 2000 ->  0.1ms * 200 = 20ms -> 50Hz

0.1ms * 500 = 50ms


0.5ms -- -90度  

150 -- 0度

crr = 250 -- 90度


hclk = 72000000 

hclk 100000 / s-psc   => 100k -- 10us

20ms = 10us*100 *20


0.5ms = 500us = 10us *  50

150ms = 10us * 100*150


1Hz - 100us*10*1000


使用這個設定

hclk 100000 / s-psc     => 100k -- 10us                                             
2000 s-arr  => 20ms -> 50Hz                                                                              
50 s-ccr   ok.       (0 度)                                                                         
150 s-ccr  ok.      (90 度)                                                                        
250 s-ccr  ok.     (180 度)









2024年2月4日 星期日

在 STM32F103 上執行 Mecrisp-Stellaris Forth

Forth,是一種宗教

1980 年代,黃大一 (1948-12-26 -- 2023-01-18) 所撰寫的《符式(FORTH) F83 入門 》(松崗出版社 1986) 是我接觸 Forth 的開始。黃大一對 Forth 如宗教般的推崇,也反應了許多人對 Forth 如宗教般的信仰。雖然如此,因為 Forth 的特性,在一般電腦上其實無法和其他語言競爭,其強項為兼具 interactive/compiling 和精簡的基本系統,很適合在嵌入式系統上使用。

因此,我雖然很早就接觸 Forth,但幾乎沒有實際派上用場。直到最近 (2024),一片獨立的 32位元單晶片系統,如 stm32f103,不到台幣 100元,最適合在上面執行 Forth 了。

Mecrisp-Stellaris Forth

下載版本 mecrisp-stellaris-2.6.5,使用 precompiled 目錄下的 mecrisp-stellaris-stm32f103-with-usb-terminal.bin。使用 ST-LINK 燒錄時,不用切換 BOOT0 的狀態。

連上之後,傳回的裝置為 

---------------------------------
[1217966.500259] usb 1-7.3.1: new full-speed USB device number 16 using xhci_hcd
[1217966.591866] usb 1-7.3.1: New USB device found, idVendor=0483, idProduct=5740, bcdDevice= 2.00
[1217966.591871] usb 1-7.3.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[1217966.591872] usb 1-7.3.1: Product: Forth Serial Port
[1217966.591873] usb 1-7.3.1: Manufacturer: Mecrisp (STM32F10x)
[1217966.591874] usb 1-7.3.1: SerialNumber: 0F775724
[1217966.600191] cdc_acm 1-7.3.1:1.0: ttyACM0: USB ACM device
--------------------------------

連接指令 "sudo minicom -b 115200  -D /dev/ttyACM0",要修改設定,加入在按 Enter 時,送出 CRLF。按鍵順序 Ctrl-A -> Z -> U,即可 "開啟加入換列字元" 或 "關閉加入換列字元"。

--------------------
STM32F103 的 USART I/O
USART1:PA9 - TX,PA10 - RX
USART2:PA2 - TX,PA3 - RX
USART3:PB10 - TX,PB11 - RX
--------------------

假如燒錄完成,連線之後,不斷出現 "Unhandled Interrupt 00000003 !",可能是因為 flash 中有其他先前存在的程式,可以將 flash 完整清除看看,參考 Bugs or Benefits ?

差別在於,stm32f103 的 memmap 定義如下

--------------------
MEMORY
{
   rom(RX)   : ORIGIN = 0x00000000, LENGTH = 0x4000
   ram(WAIL) : ORIGIN = 0x20000000, LENGTH = 0x4000
}
--------------------

stm32f103-ra 的 memmap 定義則為

--------------------
MEMORY
{
   rom(RX)   : ORIGIN = 0x00000000, LENGTH = 0x5000
   ram(WAIL) : ORIGIN = 0x20000000, LENGTH = 0x4000
}
--------------------

相關說明 RA Kernel,"Activate RA compiler optimisations by increasing the reserved core size to 0x5000 (up from the 0x4000 of the classic cores) and by adding an assembler switch"。


Forth 專用連工具 e4thcom - A Terminal for Embedded Forth Systems,方便下載程式。

連線指令 "sudo ./e4thcom -t mecrisp-st -d ttyUSB0 -b B115200 --idm"

ascii      ascii-xfr -dsv -l 200  










網誌存檔