ARM Cortex M0 Artykuły na programistamag 73 74 75 Rafała Kozika

0

Tu nie ma co "rozkminiać". PA13 i PA14 to porty sprzętowego debugera, rys. 10 tutaj.
https://www.st.com/content/ccc/resource/technical/document/user_manual/e3/0e/88/05/e8/74/43/a0/DM00231744.pdf/files/DM00231744.pdf/jcr:content/translations/en.DM00231744.pdf
Nie chce mi się szukać teraz w RM dokładnych ustawień, ale na z dużym prawdopodobieństwem wpisując do rejestru konfiguracyjnego 0 pod odpowiedzialne za nie bity odpinasz sobie debuger. Przy resecie te rejestry ustawiają się do wartości domyślnej. Patrz RM 32.3.1 i 32.3.2.

0

Miałem przerwę, dziś z ciekawości zacząłem sprawdzać co siedzi w pamięci (domyślnie). I albo czegoś nie rozumiem albo... ?!
Tutaj jest dokumentacja do płytki

#1
Więc najpierw chciałem zobaczyć co jest pod adresem dla RCC, czyli adres 0x40021000, dział 2.2.2 Memory map and register boundary addresses oraz 6.4.15 RCC register map. Czyli to jest adres z offsetem 0, RCC_CR. Mniej więcej wszystko jest ok, wartość jaka jest pod tym adresem to 50351255 decymalnie, w hex 0x3004c83, biranie 11000000000100110010000011. Więc analizując wygaszone i zapalone bity w tabelcce 6.4.15 RCC register map > RCC_CR poza bitami 14, 11, 10 i 7 które są ustawione na 1 (zapalone), nie wiem po co reszta wydaje się ok, ustawione są HSI oraz PLL.

#2
Ale przy USART1 już się zdziwiłem bo dostałem wartośc 20, czyli 0.14. O ile pierwsze bity mogłby się zgadzać to nie zgadza mi się że nie jest zapalony bit dla GPIOB, binarnie kod powinien wyglądać 1000000000000000100, czyli 18 bit zapalony, i decymalnie 262148, hex 40004 (mniej wiecej bo teraz na szybko liczę), a jest... 10100, dec 20, hex 0x14


Ogólnie chodzi mi o to co robię źle odczytując zawartość pamięci. Chciałbym wiedzieć co jest domyslnie ustawione pod wskazanymi adresami, ale wartości które dostałem trochę mi nie poasują do tabeli z dokumentacji.

#1 - RCC
Screenshot from 2019-12-11 1552.pngScreenshot from 2019-12-11 1510.png

#2 - USART1
Screenshot from 2019-12-11 1508.pngScreenshot from 2019-12-11 1640.png

I jeszcze kod main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>© Copyright (c) 2019 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
//UART_HandleTypeDef uart;

static int n = 0;

void send_string(char* s)
{
 HAL_UART_Transmit(&huart1, (uint8_t*)s, strlen(s), 1000);
}

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  asm volatile ("ldr r0, =1");

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  float fn = 20.0284928592f;
  asm volatile (".set TEST, 0x40021000");
  asm volatile ("ldr r0, =TEST");
  asm volatile ("ldr r1, [r0]");

  // rcc ahbenr
  asm volatile (".set TEST2, (0x40021000 + 0x14)");
  asm volatile ("ldr r0, =TEST2");
  asm volatile ("ldr r1, [r0]");

  asm volatile (".set USART1TEST, 0x40013800");
  asm volatile ("ldr r0, =USART1TEST");
  asm volatile ("ldr r1, [r0]");
  while (1)
  {
    /* USER CODE END WHILE */
	  //char ss = (char*) n;
	  //send_string(ss);
	  //n = n + 1;
	  //int c = n;
	  char buffer[100];
	  //itoa(n, x, 16);
	  //x = x +"\r\n";
	  //send_string(x);
	  //sprintf(buffer, '%d', n),
	  //int p = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7);
	  //HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), 1000);
	  //HAL_UART_Transmit(&huart1, (uint8_t*)buffer, sprintf(buffer, "%d \r\n", p), 500);
	  HAL_UART_Transmit(&huart1, (uint8_t*)buffer, sprintf(buffer, "%s %d\r\n", "Hello",  n), 500);
	 // HAL_UART_Transmit(&huart1, (uint8_t*)buffer, sprintf(buffer, "%s %g %e %f\r\n", "Hello2", fn, fn, fn), 500);
	  n = n + 1;
	  fn += 0.001f;
	  HAL_Delay(100);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  //GPIO_PinState GPIO_PIN_SET = 0;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3|GPIO_PIN_6, GPIO_PIN_RESET);

  /*Configure GPIO pins : PB3 PB6 */
  GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pin : PB7 */
  GPIO_InitStruct.Pin = GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  //GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(char *file, uint32_t line)
{ 
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
0

Zrób po inicie GPIOB albo przed odczytem AHBENR chamskiego dileja typu for (volatile int i = 0; i < 100000; ++i); a potem zobacz czy to pomogło.
EDIT: pytanie też czy to leci krokowo, czy masz breakpointy w pewnych miejscach i ogólnie to jak haltujesz procka może mieć znaczenie.

0
alagner napisał(a):

Zrób po inicie GPIOB albo przed odczytem AHBENR chamskiego dileja typu for (volatile int i = 0; i < 100000; ++i); a potem zobacz czy to pomogło.
EDIT: pytanie też czy to leci krokowo, czy masz breakpointy w pewnych miejscach i ogólnie to jak haltujesz procka może mieć znaczenie.

Wstawiłem w kilku miejscach, ale dalej wyświetla 0x14. Co dziwne to w pamięci pod adresem kiedy zrobię zrzut w gdb poleceniem x/64bx 0x40021000 pokazuje że jest tam wartość 40014, binarnie 1000000000000010100, czyli jest ten 18 bit zapalony, bo z biblioteki i z listingów Rafała zapalenie tego bitu robi się wpisując wartość tego przesunięcia do tego rejestru (1 << 18) i w ten sposób konfiguruje się ten port (mniej więcej). Anyway... chyba znowu coś namieszałem... aż sam się pogłubiłem teraz w tym. Na niebiesko jest zrzut z pamięci pod tymi adresami... I tam wygląda że jest ok, a do rejestru wrzuca tylko pierwszy bajt, zamiast całe 4 bajty. Tam pod 0x40021000 jest wartość 3004c83, czyli to co na pierwszym rysunku z postu wyżej w lewym dolnym rogu. I tutaj zrzuciło całe 4 bajty z pod tego adresu pamięci, a z pod 0x40021014 tylko pierwszy bajt. Dobra, bo już sam się pogłubiłem w tym ;/

Screenshot from 2019-12-12 0050.png

0

To już wiadomo, że problem leży w odczycie.
Zrób coś w stylu 'uint32_t test = RCC->AHBENR;' i zobacz a. czy to ma dobrą wartośc b. jak to kompilator rozwija do asemblera. Może chodzi o alignment?

0
alagner napisał(a):

To już wiadomo, że problem leży w odczycie.
Zrób coś w stylu 'uint32_t test = RCC->AHBENR;' i zobacz a. czy to ma dobrą wartośc b. jak to kompilator rozwija do asemblera. Może chodzi o alignment?

Dzięki za oddzew. Jest już zdefinioway ten typ jako uint32_t. Tutaj jest ten fragment ze standardowej biblioteki dołączonej przez stm32cube, plik stm32f0xx_hal.h

#define __HAL_RCC_GPIOB_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg; \
                                        SET_BIT(RCC->AHBENR, RCC_AHBENR_GPIOBEN);\
                                        /* Delay after an RCC peripheral clock enabling */\
											asm volatile (".set TEST2, (0x40021000 + 0x14)");\
											asm volatile ("ldr r0, =TEST2");\
											asm volatile ("ldr r1, [r0]");\
                                        tmpreg = READ_BIT(RCC->AHBENR, RCC_AHBENR_GPIOBEN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)

Próbowałem zrobić uint32_t temp = SET_BIT(RCC->AHBENR, RCC_AHBENR_GPIOBEN); ale tak samo nic to nie dało. W końcu wstawiłem te 3 linijki assemblera między ten kod, tak jak tutaj widać, dałem pułpakę, na __HAL_RCC_GPIOB_CLK_ENABLE(); w pliku main, w lini 240, jak pokazywałem to na obrazku wyżej, i krok po kroku sprawdzałem co mi wstawi do rejestru. No i wstawiło liczbę 262164, czyli hex 40014, czyli jest to co ma być. Ale gdziekolwiek indziej wstawię ten kod to dalej mam 20. Może to kwestia jakiegoś resetu GPIOB, coś jeszcze się zmienia między czasie. Albo read_bit zmienia coś po drodze. Muszę to dokładniej jednak sprawdzić. W każdym razie w tym miejscu ustawia te bity (chyba) w tym miejscu, między tymi funkcjami.

Narobiłem tylko zamieszania znowu... ale dzięki za pomoc. Częściowo problem udało się rozwiązać z pomocą, znowu ;)

0

Nie zrozumielliśmy się chyba. Odczytaj ten rejestr do zmiennej używając C, sprawdź jej wartość i (o ile ona będzie dobra) zobacz do jakiego asma się to skompiluje. Bo możliwe, że kompilator tam wsadzi dodatkową synchronizację/rozbije to na kilka odczytów halfword/byte... no cokolwiek. Możesz jeszcze przeglądnąć erratę chipu, wątpię, żebyś coś znalazł, ale może.

0
alagner napisał(a):

Nie zrozumielliśmy się chyba. Odczytaj ten rejestr do zmiennej używając C, sprawdź jej wartość i (o ile ona będzie dobra) zobacz do jakiego asma się to skompiluje. Bo możliwe, że kompilator tam wsadzi dodatkową synchronizację/rozbije to na kilka odczytów halfword/byte... no cokolwiek. Możesz jeszcze przeglądnąć erratę chipu, wątpię, żebyś coś znalazł, ale może.

Hm, nie wiem czy teraz jest to o czym piszesz... No więc zrobiłem zmienną tmp ale jako globalna zmienna na początku pliku stm32f0xx_hal_r.h, poza tą funkcją __HAL_RCC_GPIOB_CLK_ENABLE(). Wstawiłem jeszcze 2 nopy przed i za żeby wiedzieć gdzie to się zaczyna i kończy w kodzie assemblera. Ogólnie jeśli zdefiniuję to od razu tutaj w tej definicji funkji jako uint32_t tmp = SET_BIT.... to wartość będzie już przypisana, A przy zmiennej globalnej dopiero pod koniec przypisuje wartość. Wyświetliłem w gdb print tmp co każdą instrukcję, widać że dopiero na nopie końcowym ustawiło wartośc dla tmp. Wyświetliłem też wartość w konsoli przez sprintf bo mam to zainicjalizowane tutaj akurat żeby debugować, ale już bez breakpointów i też jest wartość 262164. Czyli Ok. I teraz to już całkowicie zgłupiałem :)

#define __HAL_RCC_GPIOB_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg; \
                                        /*SET_BIT(RCC->AHBENR, RCC_AHBENR_GPIOBEN);*/\
										asm volatile ("nop");\
                                        tmp = SET_BIT(RCC->AHBENR, RCC_AHBENR_GPIOBEN);\
                                        asm volatile ("nop");\
                                        /* Delay after an RCC peripheral clock enabling */\
                                        tmpreg = READ_BIT(RCC->AHBENR, RCC_AHBENR_GPIOBEN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)

Screenshot from 2019-12-12 1419 (copy).png

0

Olej te magiczne makra. Mówię o czymś takim (nie wiem czy to się skompiluje, ale chodzi o ideę)

//wlasciwy header dla Twojego modelu uC, jakkolwiek on sie nazywa
#include "stm32fxxxx.h" 
#include <stdint.h>


int main(void)
{
  uint32_t regval;
  RCC->AHBENR |= RCC_AHBENR_IOBEN;
  regval = RCC->AHBENR;
  while(1);
}

A potem wolasz arm-none-eabi-objdump -D plik_wynikowy.elf

EDIT: chodzi o to jak kompilator rozwinie przypisanie do zmiennej regval.

EDIT2: i pytanie drugie: czy wartość zmiennej regval będzie właściwa w debugerze.

0

Sorry za posta pod postem... ale...
Zwróć uwagę, że kompilator robi
ldr r2, [r3 #20] zamiast dodawać 20 do r3.
Nie trafiasz w endianess/alignment?

0

Ok, zrobiłem według Twojego listingu. Po wejściu do pętli ustawia regval na 262164, czyli ok.

Screenshot from 2019-12-12 1528.png

arm-none-eabi-objdump -D , tylko kod spod etykiety main dla porównania z tym co jest w STM32Cube


08000108 <main>:
 8000108:	b580      	push	{r7, lr}
 800010a:	b082      	sub	sp, #8
 800010c:	af00      	add	r7, sp, #0
 800010e:	4b05      	ldr	r3, [pc, #20]	; (8000124 <main+0x1c>)
 8000110:	695a      	ldr	r2, [r3, #20]
 8000112:	4b04      	ldr	r3, [pc, #16]	; (8000124 <main+0x1c>)
 8000114:	2180      	movs	r1, #128	; 0x80
 8000116:	02c9      	lsls	r1, r1, #11
 8000118:	430a      	orrs	r2, r1
 800011a:	615a      	str	r2, [r3, #20]
 800011c:	4b01      	ldr	r3, [pc, #4]	; (8000124 <main+0x1c>)
 800011e:	695b      	ldr	r3, [r3, #20]
 8000120:	607b      	str	r3, [r7, #4]
 8000122:	e7fe      	b.n	8000122 <main+0x1a>
 8000124:	40021000 	andmi	r1, r2, r0

A jeśli chodzi o drugi post to nie bardzo wiem o co chodzi z endianess/alignment, dlatego teraz nie odpowiem. Muszę sprawdzić w necie co to jest najpierw ;/
Ale ogólnie wygląda ok.

0

To jeszcze wrzuce, żeby było... 262164 decimal = 40014 hex = 1000000000000010100 binary
pogrupowane po 4 bity żeby było czytelniej100 0000 0000 0001 0100
I jest to co ma być. Bit dla IOPBEN ustawiony.

ustawienie bitow rcc_ahbenr.png
Ok, na razie tyle, dzięki alagner.

0

Proszę, aczkolwiek trochę się pogubiłem na czym stanęło. Rozumiem, że to co Ci radziłem zrobić działa? No to dobrze. Tylko pytanie czemu po Twojemu nie działało ;P

0

Ok, czas na przerwania... Skonfigurowałem SysTick, jest pułapka w gdb na wejście do SysTick_Handler, widać na pierwszym obrazku, że wchodzi w tryb przerwania, rejestr LR i jego wartość zgadzają się z opisem (m.in. programistamag nr 74). Kiedy jest przerwanie z SysTick, wtedy jest wejście do funkcji SysTick_Handler. Początkowo nie było tam tych dwóch funkcji task1 i task2. Dopiero teraz je dodałem żeby zrobić ten test. Naiwnie myślałem że SysTick po wygenerowaniu przerwania wyjdzie z tej martwej pętli w task2, że ją po prostu przerwie i wróci do SysTick_Handler. Ale to tak nie działa. Może to dobrze, może nie. Nie wiem. Ale spodziewałem się właśnie takiego zachowania, a nie że utknie w tej pętli i koniec. SysTick działa w tle, jak widać na drugim obrazku, bo w debugerze wywołałem co chwilę SysTick->VAL która zwraca wartoś tego licznika. Licznik jest ustawiony na wartość 8000 i zlicza do zera, po czym jest generowane przerwanie (chociaż ciężko mi to stwierdzić, na razie ufam że tak jest) i skok do pod SysTick_Handler. Czytałem że watchdog jest rodzajem timera który może wykonać reset systemu. Jest też podział na priorytety przerwań i te o wyższym może przerwać (może wejść w) przerwanie o niższym. Czy coś takiego. Jeszcze do tego nie doszedłem. W sumie dopiero dziś się tym zająłem, więc może znowu niepotrzebnie umieszczam post, może jutro, pojutrze albo za jakiś czas dojdę do tego, ale z drugiej strony może ktoś coś wie na ten temat...

Pytanie - czy da się zrobić żeby przerwanie SysTick wyszło z tej pętli while(1) i znowu skoczyło pod SysTick_Handler. Jeśli tak, to jak ?!

Screenshot from 2019-12-26 2212.pngScreenshot from 2019-12-26 2205.png

0

Dobra, początkowo nie ogarnąłem o co chodzi. Tak patrzę teraz na tą prezentacje na temat przerwań na YT. I zamiast w głównym kodzie wstawiłem gaszenie i zapalanie diody w sekcji dla przerwania (w handlerze systick). Czyli coś takiego jest teraz. Dalej prgram utknął w pętli while(1) ale jednak przerwania są robione. W tym handlerze umieściłem zwiększanie licznika HAL_IncTick() (to może być zwykły int, ale ma taką definicje __IO uint32_t uwTick, to tak btw). I skoro licznik się zmienia to znaczy że przerwania działają mimo że program utknął w martwej pętli. Więc zrobiłem test z gaszeniem i zapalaniem diody na PB3, czyli dioda która jest wbudowana na płytce. Jeszcze dla pewności sprawdziłem wartość uwTick w debugerze. Jest ok. Nie ogarnąłem na początku o co chodzi, w sumie dalej nie wiem jak to wykorzystać jako systemowy timer, przełączenie kontekstu, sekcje krytyczne, jak to połączyć skoro w sekcji krytycznej przerwania mają być wyłączone, no ale przynajmniej zagadka z wczoraj jest rozwiązana - tzn przerwania jednak działają, tylko źle to zinterpretowałem.

Ogólnie to podchodzę do tematu tego RTOS, ale najpierw musiałem ogarnąć jakiś timer.

void SysTick_Handler(void)
{
  HAL_IncTick(); // licznik który ma się zwiększać jeśli było przerwanie

  if (uwTick > 1000) {
	  GPIOB->ODR = ~GPIOB->ODR; // negacja bitów. Jeśli jest 0 zmienia na 1, jeśli jest 1 zmienia na 0.
	  uwTick = 0;
  }
  //delay(10000);
  //update();
  //task1();
  //task2();
}

Screenshot from 2019-12-27 1057.png
Screenshot from 2019-12-27 1032.png

0

Nie do końca wiem jakie zadałeś pytanie ;)
Co widzę, że zrozumiałeś źle: współdzielona pomiędzy programem a przerwaniem (czy w *nixach sygnałem...jeden pies) zmienna nie może być zwykłym intem. __IO to na 99% jakieś przedefiniowanie volatile (sprawdź w headerach). Oznacza ono tyle "ta zmienna może się zmienć poza sekwencyjnym biegiem programu, nie optymalizuj dostępu".
Przykładowo weź sobie pętlę for (int i =0; i < 999; ++i) {} i zwróć uwagę, że jest ona pusta. Normalnie kompilator może (nie musi, ale bezpiecznie założyć, że to zrobi) całość pętli wytnie i przypisze do i 999, bo nigdzie nie występują tzw. efekty uboczne (mogę nieco upraszczać, jeśli komuś się chce to tłumaczyć dokładnie - śmiało). Albo wpakuje zmienną i od razu do rejestru i pominie jej odczyt i zapis do pamięci. volatile tego zabrania.
No ale teraz wyobraź sobie, coś takiego

int x = 0; //brak volatile

void isr_timer(void)
{
  ++i;
}

int main()
{
 timer_irq_assign(isr_timer); //wlaczenie przerwania od timera i przypisanie handlera.
 if (i == 1000) {
   alarm();
 } else {
   do_stuff();
 }
}

efekt będzie (z włączoną optymalizacją) taki:

int x = 0; //brak volatile

void isr_timer(void)
{
  ++i;
}

int main()
{
 timer_irq_assign(isr_timer); //wlaczenie przerwania od timera i przypisanie handlera.
 do_stuff(); 
}

bo w (sekwencynym) toku programu i się nie zmienia.
EDIT:
a wywłaszczanie przerwań poza RTOSem/zaawansowanym programem to na 99% coś jak friend w C++ - błąd projektowy. Tzn. inaczej: można. Ale jeśli nie robisz jakichś krytycznych optymalizacji, można dość bezpiecznie założyć, że sam fakt rozważania tego oznacza, że coś robisz bardzo źle (znowu celowo upraszczam) - być może parę warstw wcześniej.

0

heh, no zgadza się, __IO to inaczej volatile. Definicja __IO.

#define     __IO    volatile

Co do tego przykładu z pętlą. Tak, spotakałem się z takim zachowaniem kompilatora, że krótkie, puste pętle w ogóle wycinał z kodu, nie było takie pętli. Tak samo nieużywane zmienne. Tak samo zmienne typu register, jeśli ich użycie nie jest wystarczające. Np dla pętli która ma 10000000 zrobi normlanie pętle, a ten register sprawi że operacje będą na rejestrze zamiast na stosie, ale jak już dasz w pętli np te 999 to może w ogóle takiej pętli nie zrobić. To już się z takim zachowaniem kompilatora spotkałem. Coś tam już czytałem też o optymalizacjach, potkach, że procesor może potem wykonywać poza kolejnością programu instrukcje itp. co tez ma związek z kompilowanym kodem bo nie ma tam wstawionej bareiry sychronizacji. Coś tam czytałem o tym ;p

Jeśli chodzi o sam RTOS to mam ten artykuł z programistamag, poza tym kod rados, freertos bo jest w bibliotece stm32cube, więc to już jakoś będę powoli krok po kroku ogarniał na spokojnie teraz. Też czytam ANSI C więc tak kompletnie zielony nie jestem, i wiem że mogę dać strukture, i przez tą strukture sterować (synchronizować) kod zmieniając tam jakieś wartości. Na razie to testuję jak to się zachowuje, np czy takie długie pętle wewenątrz głównej pętli programu nie będą wpływały na przerwania, ale nie.

Ogólnie jestem już w domu jeśli chodzi o SysTick. Kilku rzeczy jeszcze nie wiem, ale... na razie to czego potrzebowałem mam.

Takim kodem testowałem jak będzie migała dioda, kiedy w głównej pętli programu coś tam się dzieje, są długie pętle... czy przerwanie będzie działać... I co ten program robi... Jak licznik ll jest poniżej 500000 wtedy się po prostu zwiększa, a potem zapala diode, ustawia bit 1 << 3. I tutaj jest właśnie synchornizacja między timerem, bo bez sterowania task->sp od razu zadziałało by przerwanie a tam jest negacja bitów, a tak delay(3000000) wykonuje się normalnie do końca, Dioda chwile świeci. Potem wartość ll się zeruje. I wracamy do punktu wyjścia, gdzie jest na nowo inkrementacja ll i zerowanie task->sp które powoduje że w przerwaniach znowu dioda miga. I takie cuś testuje na razie żeby to lepiej zrozumieć.

fragment struktury os_task w main.h która jest dla "synchronizacji" kodu z głównej pętli programu z timerem

typedef struct {
	uint32_t sp;
} os_task;

os_task t;
static os_task *task;

SysTick_Handler który wykonuje się przy każdym przerwaniu

void SysTick_Handler(void)
{
	task = &t;
  HAL_IncTick();

  if (uwTick > 100) {
	  if (task->sp == 0) {
	  GPIOB->ODR = ~GPIOB->ODR;
	  }
	  uwTick = 0;
  }
  //delay(10000);
  //update();
  //task1();
  //task2();
}

main.c

int ll = 0;

int main(void)
{
  //__NVIC_EnableIRQ((1UL << __NVIC_PRIO_BITS) - 1UL);
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();

  RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
  GPIOB->MODER = 1 << 6;
  GPIOB->ODR = 1 << 3;

  uint32_t k;
  task = &t; // wskaźnik to struktury os_task

  int a, b, c;
  register int r;
  int temp;

  for (a=0; a< 10000000; a++);
  for (r=0; r < 10000000; r++);

  while(1) {
	  if (ll > 500000) {
	
		  task->sp = 1;  // sterowanie wartością dla synchronizacji między timerem
	          //GPIOB->ODR = ~GPIOB->ODR;
		  GPIOB->ODR = 1 << 3;
		  delay(3000000);
		  ll = 0;
	  } else {
		  ll++;
		  task->sp = 0; // sterowanie wartością dla synchronizacji między timerem
	  }
  }

  // trap
  while(1);
}
0

Skoro już spamuje tutaj, to zrobię krótką notatkę na temat "context switching". Póki co próby kończą się hard fault z wartością -15 w rejestrze LR. Nie wiem co to znaczy, ale program się wysypuje. Ale od początku. Po wejściu do trybu przerwania automatycznie są wrzucane na stos rejestry R0, R1, R2, R3, R12, LR, PC, xPSR. I kiedy program jest w trybie przerwania, a dokładnie w SysTick_Handler (bo tam sobie ustawiam pułapkę w gdb na wejściu tak jak widać na pierwszym obrazku) mogę podmienić wtedy wartość tego co zostało odłożone na stos. Między innymi adres LR, albo PC. Jeśli wyzeruje ten fragment pamięci set {int[16]} adres_sp = {} to na wyjściu z przerwania zaliczę hard fault, bo te zapisane wartości zostały wyzerowane, i skok będzie pod adresy 0x00000000. Ale póki co testowałem zachowanie z podmianą adresu PC, adres LR nie zmieniałem (drugi obrazek). I po mękach (bo początkowo kombinowałem jak podmienić adres PC przez pop, dopiero potem się zorientowałem że trzeba podmienić to co zostało autmatycznie odłożone na stosie) udało się zachować powrót do przerwania wątku po skoku pod jeden z "tasków", co widać na obrazku 3, że wykonało się 273 powótrzenia pętli i nastąpiło przerwanie. Zrzut zawartości stosu. Widać tam że adres PC zachował aktualnie przerwaną funkcję, ale adres LR wskazuje na jedną z instrukcji w main. Zmierzam do tego, że póki co udało mi się opanować sytuację z podmianą PC, nie bawiłem się póki co w pisanie mechanizmów wybierania zadania, tylko zrobiłem na sztywno tablice (tak dokładnie to ona jest wypełniana w funkcji main tasks[9] = &task10 gdzie, chociaż początkowo było to zapisanie tego w struklturze jako wskaźnik void (*ptr)(void) i w main task->ptr = &task10 i task->ptr do tablicy, ale to już bla bla bla i tak tego nie używam jeszcze), w 9 indeksie wstawiłem adres (pointer) funkcji void task10 i ten kod asemblera właśnie pobiera adres początku tablicy tasks, potem wartość 36 to offset, bo 4 x 9 = 36 (wielkość int to 4 x 9 pozycja w tablicy), następnie zapisanie tej wartości na stos, tak samo offset żeby podmienić wartość dla rejestru PC. Początkowo wydawało się proste, ale na razie się trochę z tym męczę... Na razie do tego momentu doszedłem i póki co tak jak napisałem zaliczam hardfault ;/ Coś tam mi miesza tak że zmienia adres stosu na wartość tasks. Ale tak ogólnie do tego momentu jest nieźle, po wejściu do pętli delay wewnątrz funkcji task10 nadal mam przerwania, więc jestem na dobrej drodze żeby to rozkminić. A to są moje wnioski na ten temat póki co.

funkcja task10

void task10(void) {
	while(1) {
        if (ll > 275) { // 500000 ale zmieniłem na 275 żeby szybciej debugować
            task->sp = 1;
			GPIOB->ODR = 1 << 3;
			delay(1); // tak samo zmieniłem na 1 z 100000 żeby debugować
			ll = 0;
		} else {
	        ll++;
			task->sp = 0;
		}
    }
}

Screenshot from 2019-12-29 0738.png
Screenshot from 2019-12-29 0728.png
Screenshot from 2019-12-29 0823.png
Screenshot from 2019-12-29 0907.png

0

Kolejny spam. Po tym jak zobaczyłem hard fault zrobiony przez stack overflow, wolałem poszukać bardziej bezpiecznego rozwiązania do budowania RTOS, do eksperymentów i nauki. Ten krótki kod obsługuje 2 rodzaje przerwań SIGALRM oraz SIGINT (Linux). Pierwszy jest generowany autmatycznie co 1 sekunde, drugi wywołuje się wciśnięciem CTRL + C. Przerwania potrzebne do zabawy zamiast na NUCLEO, na którym mogę nadpisać pamięć której nie powinienem... lepiej bawić się z tym... Drugie przerwanie trzymając wciśnięte klawisze przez cały czas będzie przerywane SIGALRM-em, więc to jest właśnie dobre, bo mam 2 timery, jeden coś ala systick drugi coś jak przerwanie z GPIO itp.

Źródło https://www.bogotobogo.com/Linux/linux_process_and_signals.php

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>

volatile sig_atomic_t print_flag = false;

volatile int t1;

void task1() 
{
    t1++;
    //asm volatile ("ret");
}

void task2() 
{
    t1=0;
    //asm volatile ("ret");
}


void handle_alarm( int sig ) {
    print_flag = true;
    if (t1 > 2) {
        asm volatile ("call task2");   
    } else {
        asm volatile ("call task1");
    }
    printf( "Hello %d\n", t1 );
}

void my_signal_interrupt(int sig)
{
  printf("I got signal %d \n", sig);
  //(void) signal(SIGINT, SIG_DFL);
}



int main()
{
  (void) signal( SIGALRM, handle_alarm ); // Install handler first,
  alarm( 1);
  (void) signal(SIGINT,my_signal_interrupt);

  while(1) {
        if ( print_flag ) {
        print_flag = false;
            alarm( 1 );
        }
      //printf("Waiting for interruption...\n");
      sleep(1);
  }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1