Keil MDK + TI MSP432P401R + FreeRTOS

<rawat.s>
7 min readFeb 27, 2021

--

​ ​ ​

บทความนี้สาธิตการใช้งานซอฟต์แวร์ Keil MDK-Lite สำหรับบอร์ด MSP432 ของบริษัท Texas Instruments (TI)

ขั้นตอนการดำเนินการ

  • เรียกใช้ Pack Installer ของ MDK เพื่อติดตั้ง Software Packs สำหรับ MSP432P4xx Series (เวอร์ชันที่ใช้คือ 3.2.6)
  • สร้างโปรเจกต์ใหม่ใน uVision IDE เพื่อใช้งานกับบอร์ด MSP432 โดยยังไม่ใช้ FreeRTOS
  • ทดลองโค้ดตัวอย่างที่สาธิตรูปแบบการเขียนโค้ดในระดับรีจิสเตอร์ผ่านทางพอยน์เตอร์สำหรับโครงสร้างข้อมูล
  • ทำขั้นตอน Build Target และทดสอบการทำงานของโค้ดใน Debug Session
  • สร้างโปรเจกต์ใหม่ใน uVision IDE เพื่อสาธิตการใช้งาน FreeRTOS

​ ​ ​

รูป: ขั้นตอนการติดตั้ง Software Packs สำหรับ MSP432P4xx

สร้างโปรเจกต์ใหม่ (เช่น ตั้งชื่อ msp432_demo-1)

เลือกอุปกรณ์เป้าหมาย (Target Device) เป็น MSP432P401R

รูป: เลือก MCU ที่จะใช้งาน (MSP432P401R)

​ ​ ​

จากนั้นจะต้องเลือกคอมโพเนนต์สำหรับ RTE (Run-Time Environment) และให้เลือกเพียง CMSIS-CORE และ Device Startup เท่านั้น สำหรับการทดลองเขียนโค้ดในเบื้องต้น

รูป: เลือก RTE Components

​ ​ ​

เมื่อสร้างโปรเจกต์ใหม่แล้ว ให้สร้างไฟล์ main.c ไว้ในโปรเจกต์ โดยเลือกจาก User Code Template — Empty C main

รูป: สร้างไฟล์ main.c จาก User Code Template

จากนั้นลองเปิดดูไฟล์ main.c

ถัดไปให้เลือกคอมไพล์เลอร์ที่จะใช้งานก่อน โดยให้เลือกเป็น ARM Compiler v5 (armcc) แทนการใช้ ARM Compiler v6 (armclang) ที่ใหม่กว่า

​ ​ ​

ข้อสังเกต: ถ้าเลือกเป็น ARM Compiler v6 จะเกิดข้อผิดพลาดในการคอมไพล์โค้ด ดังนั้นให้ใช้ ARM Compiler v5 แทนไปก่อน

​ ​ ​

รูป: เลือกใช้ ARM Compiler Version 5

ให้ลองแทนที่โค้ดใน main.c ด้วยโค้ดต่อไปนี้ ซึ่งเป็นการสาธิตการเขียนโค้ดเพื่อทำให้ LED1 บนบอร์ด ซึ่งเป็นวงจรที่ต่อเข้ากับขา P1.0 อยู่บนบอร์ดทดลอง

เริ่มต้นการทำงานของโค้ดด้วยการกำหนดให้ขา P1.0 เป็นเอาต์พุต โดยการเขียนค่าบิตเป็น 1 ลงในรีจิสเตอร์ P1DIR ในตำแหน่งบิต 0 (ตรงกับขา P1.0) และถัดไปเป็นการเขียนค่าบิตเป็น 0 หรือ 1 สลับกันไป ลงในเรจิสเตอร์ P1OUT ในตำแหน่งบิตที่ 0 และมีการเว้นระยะเวลา

#include "msp.h"#define LED_BIT       (1 << 0)    // Port P1 Bit 0
#define DELAY_CYCLES (SystemCoreClock/10)
void delay_loop( uint32_t n ) {
while (n-- > 0);
}
int main(void) {
// stop watchdog timer
WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD;
// set up bit 0 of P1 as output
P1DIR |= LED_BIT;
// initialize bit 0 of P1 to 0
P1OUT &= ~LED_BIT;
while (1) { // toggle LED
P1OUT ^= LED_BIT; // toggle the LED output
delay_loop( DELAY_CYCLES ); // software delay
}
}

การเว้นระยะเวลา จะใช้ฟังก์ชัน delay_loop()ที่ได้สร้างขึ้นมา เป็นการทำงานแบบวนลูป จากตัวนับที่มีค่าเริ่มต้นเป็นจำนวนเต็มบวก แล้วนับถอยหลังจนกว่าได้ค่าเป็น 0

การนับนี้จะใช้ทำได้ช้าหรือเร็ว ก็ขึ้นอยู่กับความถี่ของซีพียูที่ได้มีการตั้งค่าใช้งาน แต่เราก็สามารถทราบค่าความถี่ของระบบได้ โดยอ่านค่าจากตัวแปรภายนอกที่มีชื่อว่า SystemCoreClock

โดยทั่วไป ค่า Default จะเป็น 3MHz แต่สามารถเลือกใช้ความถี่จากตัวเลือกที่มีได้ เช่น 1.5MHz, 3MHz, 12MHz, 24MHz และ 48MHz (ดูได้จากไฟล์ system_msp432p401r.c)

ถ้าต้องการกำหนดขนาดของหน่วยความจำ SRAM เพื่อนำมาใช้สำหรับ

  • Software Stack เช่น ใช้สำหรับตัวแปรภายใน (Local Variables) และเมื่อมีการเรียกฟังก์ชัน
  • Software Heap เช่น ใช้สำหรับคำสั่ง malloc() หรือ free()

ก็กำหนดค่าได้ในไฟล์ startup_msp432p401r_uvision.s (หน่วยเป็นไบต์)

รูป: ตัวอย่างการกำหนดค่าสำหรับ Software Stack และ Heap

จากนั้นจึงทำขั้นตอน Build Target อีกครั้ง

ถ้าต้องการทำขั้นตอนดีบัก จะต้องตั้งค่าเพื่อเลือกใช้ Debugger ก่อน ในกรณีที่ใช้บอร์ด MSP432P401R LaunchPad ให้เลือกใช้ XDS110 Debug Probe ที่มีอยู่บนบอร์ด ซึ่งจะสามารถใช้งานได้แบบ CMSIS-DAP Debugger ทั่วไป

ถ้าเสียบสาย USB เชื่อมต่อระหว่างบอร์ด MSP432 และคอมพิวเตอร์ของผู้ใช้ และได้ติดตั้ง USB Device Driver สำหรับอุปกรณ์ดังกล่าวแล้ว ก็จะสามารถมองเห็นอุปกรณ์ และระบุ IDCODE และ Serial Number ของอุปกรณ์ได้

รูป: การตั้งค่าเพื่อใช้งานอุปกรณ์ XDS110 Debug Probe

โค้ดตัวอย่างถัดไป เป็นการเปิดใช้งานวงจร SysTick Timer (24-bit Timer/Counter) ของซีพียู และให้มีการนับถอยหลัง (Count Down) ตามจำนวนครั้งที่ต้องการ เมื่อนับครบแล้วในหนึ่งรอบ ให้สร้างอินเทอร์รัพท์ และเกิดกขึ้นด้วยอัตราคงที่ และเมื่อเกิดอินเทอร์รัพท์ในแต่ละครั้ง ก็ให้ ISR (Interrupt Service Routine) ที่เกี่ยวข้อง ทำหน้าที่สลับสถานะลอจิกของขา LED1

ข้อสังเกต: 2²⁴ -1 = 16,777,215 ซึ่งเป็นค่า RELOAD สูงสุดสำหรับ SysTick Timer และถ้าใช้ความถี่ 24MHz ค่าของตัวแปร SystemCoreClock จะเท่ากับ 24000000 และในตัวอย่างโค้ดนี้ RELOAD_VALUE จะได้ (24000000/2 –1) ซึ่งไม่เกิน (2²⁴-1)

#include "msp.h"#define LED_BIT     (1 << 0)  // Port P1 Bit 0
#define RELOAD_VALUE ((SystemCoreClock/2)-1) // we use 24MHz clock
void LED1_Init() {
// set up bit 0 of P1 as output
P1->DIR |= LED_BIT;
// initialize bit 0 of P1 to 0
P1->OUT &= ~LED_BIT;
}
void SysTick_Init() {
SysTick->LOAD = RELOAD_VALUE; // set the reload value
SysTick->CTRL = 0x7; // ENABLE bit=1: enable SysTick interrupt
// TICKINT bit=1: enable interrupt
// CLKSOURCE bit=1: use the core clock
// call the CMSIS function to set priority for SysTick interrupt
// set IRQ priority for SysTick to 2
NVIC_SetPriority( SysTick_IRQn, 2 );
}
int main(void) {
WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD; // stop WDT
LED1_Init(); // initialize the LED1 output pin
SysTick_Init(); // initialize the SysTick
__enable_irq(); // enable global interrupt
while (1) {}
}
void SysTick_Handler(void) { // ISR for SysTick interrupt
P1->OUT ^= LED_BIT; // toggle the LED1
}

อีกตัวอย่างหนึ่งเป็นการเปิดใช้งาน UART0 เพื่อส่งข้อมูลแบบบิตอนุกรม โดยใช้ค่า Baudrate เท่ากับ 115200 และเลือกใช้ขา Tx/Rx ที่เชื่อมต่อกับ XDS110 บนบอร์ดไมโครคอนโทรลเลอร์ และสามารถรับส่งข้อมูลกับคอมพิวเตอร์ของผู้ใช้ได้

#include <msp.h>
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#define BAUDRATE (115200)
#define UART_BRW_VALUE (SystemCoreClock/BAUDRATE)
#define RELOAD_VALUE ((SystemCoreClock/1000)-1)
#define LED_BIT (1 << 0) // Port P1 Bit 0
// global variables
static char sbuf[80];
static volatile uint32_t ticks = 0;
// function prototypes
void SystemTick_Init(void);
void LED1_Init(void);
void LED1_Toggle(void);
void UART0_Init(void);
void sendString( const char *);
int main(void) {
uint32_t cnt = 0, ts, saved_ts;
WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD; // stop WDT
SystemTick_Init(); // initialize SystemTick Timer
LED1_Init(); // initialize LED1 pin
UART0_Init(); // initialize UART0
sendString( "MSP432 demo...\r\n" );
saved_ts = ticks;
while (1) {
ts = ticks; // read the current tick count
if ( (ts - saved_ts) >= 1000 ) { // every 1000 msec
saved_ts = ts; // update the timestamp variable
LED1_Toggle(); // toggle LED1 output
sprintf( sbuf, "Count: %06d\r\n", cnt++ );
sendString( sbuf ); // send string to UART0
}
} // end-of-while
}

void SystemTick_Init(void) {
SysTick->LOAD = RELOAD_VALUE; // set the reload value
SysTick->CTRL = 0x7; // ENABLE bit=1: enable SysTick IRQ
// TICKINT bit=1: enable interrupt
// CLKSOURCE bit=1: use the core clock
// call the CMSIS core function to set IRQ priority for SysTick
NVIC_SetPriority( SysTick_IRQn, 2 );
}
void SysTick_Handler(void) { // ISR for SysTick / every 1 msec
ticks++; // increment tick counter
}
void LED1_Init(void) {
// set up bit 0 of P1 as output
P1->DIR |= LED_BIT;
// intialize bit 0 of P1 to 0
P1->OUT &= ~LED_BIT;
}
void LED1_Toggle(void) {
// toggle LED1 output
P1->OUT ^= LED_BIT;
}
void UART0_Init(void) {
P1->SEL0 |= 0x0C; // configure P1.3 and P1.2 for EUSCI_A0 (UART)
P1->SEL1 &= ~0x0C;
EUSCI_A0->CTLW0 |= 1; // put the EUSCI_A0 to reset mode
EUSCI_A0->MCTLW = 0; // disable oversampling
EUSCI_A0->CTLW0 = 0x0081; // 1 stop bit, no parity,
// use SMCLK, 8-bit data
EUSCI_A0->BRW = UART_BRW_VALUE; // set baudrate
EUSCI_A0->CTLW0 &= ~1; // enable EUSCI_A0 (no Tx/Rx interrupt)
}
void sendString( const char *str ) {
int i, len = strlen(str);
for ( i=0; i < len; i++ ) {
// wait until TX buffer is ready
while( !(EUSCI_A0->IFG & UCTXIFG) ) ;
EUSCI_A0->TXBUF = str[i];
}
}

ถ้าทดสอบการทำงานของโค้ดตัวอย่างนี้กับบอร์ด MSP432 จะได้ข้อความส่งมาทาง USB-to-Serial ในลักษณะต่อไปนี้

รูป: ตัวอย่างข้อความที่ได้รับจากบอร์ด MSP432

การเขียนโค้ดโดยใช้ FreeRTOS

​ ​ ​

เริ่มต้นด้วยการสร้างโปรเจกต์ใหม่ และเลือก RTE Components ตามตัวอย่าง

จากนั้นให้ลองเปิดดูไฟล์ FreeRTOSConfig.h

ให้แก้ไขไฟล์ FreeRTOSConfig.h ตามตัวอย่าง (สังเกตตำแหน่งที่มีลูกศรชี้)

ถัดไปให้สร้างไฟล์ main.c แล้วเพิ่มไว้ในโปรเจกต์ และแก้ไขตามโค้ดตัวอย่างต่อไปนี้

โค้ดตัวอย่างนี้สาธิตการใช้งาน FreeRTOS โดยสร้างทาสก์จำนวน 3 ทาสก์ ที่มีระดับความสำคัญเท่ากัน และให้แต่ละทาสก์ เข้าใช้งานเซมาฟอร์แบบไบนารีเดียวกัน

แต่ละทาสก์ จะทำให้ LED ที่เกี่ยวข้อง (ใช้ RGB LED บนบอร์ด ซึ่งตรงกับขา P2.0, P2.1 และ P2.2 ตามลำดับ) สว่างขึ้นประมาณ 0.5 วินาที แล้วดับลง

#include "msp.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
// functions prototypes
void LEDs_Config(void);
void vTask1( void *pvParameters ); // task entry function for Task1
void vTask2( void *pvParameters ); // task entry function for Task2
void vTask3( void *pvParameters ); // task entry function for Task3
#define LEN_ON_MSEC (500)
#define LED_ON(i) { P2->OUT |= (1<<(i)); }
#define LED_OFF(i) { P2->OUT &= ~(1<<(i)); }
// global variables
SemaphoreHandle_t xSemaphore = NULL;
int main(void) {
WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD; // Stop WDT
// Initialize RGB LED pins
LEDs_Config();
// Create Task 1
xTaskCreate( vTask1, "Task1",
configMINIMAL_STACK_SIZE,
(void*)NULL, tskIDLE_PRIORITY+1, NULL );
// Create Task 2
xTaskCreate( vTask2, "Task2",
configMINIMAL_STACK_SIZE+1, (void*)NULL,
tskIDLE_PRIORITY+1, NULL );
// Create Task 3
xTaskCreate( vTask3, "Task3",
configMINIMAL_STACK_SIZE+1, (void*)NULL,
tskIDLE_PRIORITY+1, NULL );
// Create a binary semaphore
xSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive( xSemaphore );
// Start the scheduler
vTaskStartScheduler();
while(1);
}
void vTask1( void *pvParameters ) {
while (1) {
xSemaphoreTake(xSemaphore,(TickType_t) portMAX_DELAY);
LED_ON(0);
vTaskDelay( LEN_ON_MSEC / portTICK_PERIOD_MS );
LED_OFF(0);
xSemaphoreGive(xSemaphore);
taskYIELD();
}
}
void vTask2( void *pvParameters ) {
while (1) {
xSemaphoreTake(xSemaphore,(TickType_t) portMAX_DELAY);
LED_ON(1);
vTaskDelay( LEN_ON_MSEC / portTICK_PERIOD_MS );
LED_OFF(1);
xSemaphoreGive(xSemaphore);
taskYIELD();
}
}
void vTask3( void *pvParameters ) {
while (1) {
xSemaphoreTake(xSemaphore,(TickType_t) portMAX_DELAY);
LED_ON(2);
vTaskDelay( LEN_ON_MSEC / portTICK_PERIOD_MS );
LED_OFF(2);
xSemaphoreGive(xSemaphore);
taskYIELD();
}
}
void LEDs_Config(void) { // use on-board RGB LED Pins
// configure P2.0, P2.1 and P2.2 as output
P2->DIR |= 0x7;
// write 0 to P2.0, P2.1 and P2.2 output
P2->OUT &= ~0x7;
}

เลือก CMSIS-DAP Debugger แล้วทำขั้นตอน Debugging

รันโค้ดโดยใช้บอร์ด MSP432 ที่เชื่อมต่ออยู่ และสามารถกำหนดตำแหน่งในโค้ดให้เป็น Breakpoints ได้

ข้อสังเกต: ถ้าต้องการจะเปลี่ยนไปใช้ Simulator หรือไม่ใช่บอร์ด MSP432 สำหรับ Debug Session ให้แก้ไขโค้ดในไฟล์ FreeRTOSConfig.h เพื่อกำหนดค่าใหม่ให้กับ configPRIO_BITS และ configLIBRARY_LOWEST_INTERRUPT_PRIORITY ตามลำดับ

#define configPRIO_BITS      4   // 3 --> 4

และ

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY  15 // 7 --> 15

จากนั้นคอมไพล์โค้ดใหม่ (Build Target) แล้วเลือกใช้ Simulator แทน Hardware Debugger

สร้างและเพิ่มไฟล์ debug.ini แล้วโดยใส่ข้อความดังต่อไปนี้ (สำหรับการอนุญาตให้ Simulator มีการเขียนอ่านข้อมูลในแอดเดรสต่าง ๆ ของ Memory Map ตามช่วงที่กำหนดไว้ได้)

MAP 0x20000000, 0x200FFFFF READ WRITE
MAP 0x40000000, 0x400FFFFF READ WRITE
MAP 0xE0000000, 0xE00FFFFF READ WRITE

จากนั้นก็ทำขั้นตอน Debug ตามปรกติ

กล่าวสรุป

​ ​ ​

เราได้เห็นขั้นตอนการใช้งาน FreeRTOS ในเบื้องต้น โดยใช้ MDK-Lite และ uVision IDE ในการเขียนโค้ดสำหรับบอร์ดไมโครคอนโทรลเลอร์ MSP432P401R

--

--

<rawat.s>
<rawat.s>

Written by <rawat.s>

I'm Thai and working in Bangkok/Thailand.

No responses yet