hyunsik-yoon / study

Personal repository for self-study
Apache License 2.0
0 stars 0 forks source link

Embedded System (feat. STM32F411RE) #164

Open hyunsik-yoon opened 1 year ago

hyunsik-yoon commented 1 year ago

let's study.

hyunsik-yoon commented 1 year ago

Driver

In bare-metal programming, a driver is a software component that abstracts the low-level details of controlling and communicating with a specific hardware peripheral, device, or component. Drivers provide a well-defined interface or API that allows higher-level software, such as applications or operating systems, to interact with the hardware without needing to know the intricate details of how the hardware works.

A driver is responsible for the following tasks:

  1. Initialization: Configuring the hardware peripheral or device, setting up its registers, and enabling the necessary clocks and power supplies.
  2. Control: Providing functions or methods to control the hardware, such as setting output values, configuring operating modes, or changing settings.
  3. Data Transfer: Facilitating data transfers between the hardware and the software, such as reading data from sensors, writing data to displays, or handling data buffers for communication interfaces like UART, SPI, or I2C.
  4. Interrupts and Event Handling: Managing interrupts generated by the hardware, and implementing appropriate interrupt handlers to respond to various events or conditions.
  5. Error Handling: Detecting and handling hardware errors or fault conditions, and reporting them to the higher-level software as needed.

In bare-metal programming, drivers are typically written in C or assembly language and are tailored to the specific microcontroller, peripheral, or device being used. They often interact directly with the hardware registers and handle low-level details such as timing, protocol requirements, or power management. The development of drivers requires a good understanding of the hardware datasheets, specifications, and reference manuals, as well as the target platform's microcontroller architecture and features.

Using drivers in bare-metal programming helps to maintain a modular and organized codebase, simplify the development process, and improve code reuse across different projects or platforms.

example: interrupt handling


- 참고: embedded linux UART 접근은 파일 접근으로 사용
```c
uart_fd = uart_init("/dev/ttyS0", B115200);
bytes_read = read(uart_fd, &rx_data, sizeof(rx_data));
hyunsik-yoon commented 1 year ago

C++ 이 C 보다 덜 사용되는 이유

  1. Code size and performance: C++ features such as exceptions, RTTI, and certain aspects of OOP can increase code size and negatively impact performance. Developers need to be cautious when using these features and may need to disable or avoid some of them in resource-constrained environments.

  2. Embedded에는 최신 C++ 미지원 컴파일러가 아직 많음: Not all embedded C++ compilers have full support for the latest C++ standards or standard libraries. Developers may need to rely on a limited subset of the C++ language or use third-party libraries specifically designed for embedded systems.

hyunsik-yoon commented 1 year ago

what happen after press "RUN" in IDE

  1. Compilation
    • ELF 나 HEX(intel) 같은 포멧으로 컴파일됨
    • ex) arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -Og -Wall source_file.c -o output_file.o
  2. Linking
    • obj들을 모으고 single exe binary 생성
    • memory layout (code, data 영역 등을 생성)
    • ex) arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Og -Wall -T linker_script.ld -o output_file.elf input_file1.o input_file2.o
  3. Conversion
    • flasing을 위한 format으로 바꿈 (HEX, BIN 형식)
    • ex) arm-none-eabi-objcopy -O ihex output_file.elf output_file.hex
  4. Downloading/Flashing
    • STM32F411RE microcontroller's flash memory로 전송
    • JTAG 또는 Serial Wire Debug(SWD) 같은 인터페이스로 연결됨
    • ex) openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
  5. Verification
    • host에 있는 파일과 flashing 된 파일이 같은지 확이
  6. Reset and run

JTAG

hyunsik-yoon commented 1 year ago

startup_stm32f411retx.s file

  1. Setting up the initial stack pointer: The startup code initializes the stack pointer to the top of the stack memory region. This is necessary for proper function call handling and local variable storage.

  2. Initializing static data: The startup code copies the initial values of global and static variables from the non-volatile flash memory to their designated locations in RAM.

  3. Zero-initializing the uninitialized data section (BSS): The startup code clears the memory region used by global and static variables that have not been explicitly initialized. This ensures that these variables start with a known value of zero.

  4. Setting up interrupt vector table: The startup code initializes the interrupt vector table, which maps interrupt sources to their corresponding interrupt service routines (ISR) or handlers. This table is stored in a specific memory region and is used by the microcontroller to look up the address of the ISR when an interrupt is triggered.

  5. Calling the SystemInit() function (if available): Some microcontrollers have a SystemInit() function that performs additional hardware initialization, such as setting up the clock configuration or configuring certain peripherals. If available, the startup code calls this function before the main application code starts executing.

  6. Calling the main() function: After completing the initialization tasks, the startup code calls the main() function, which is the entry point of the main application code.

hyunsik-yoon commented 1 year ago

힙업 스택다운

image

BSS (Block Started by Symbol)

(초기화도 안해놨니?? 불쉿..... BS...S...) 이렇게 외워 ㅎㅎ

static data

  1. Initialized static data: These are global and static variables that have been explicitly initialized by the programmer. Initialized static data is stored in a different memory segment called the data segment. When the program starts, the startup code copies the initial values of these variables from non-volatile flash memory to their designated locations in RAM.

  2. Uninitialized static data (BSS): These are global and static variables that have not been explicitly initialized by the programmer. Uninitialized static data is stored in the BSS segment, as explained above.

hyunsik-yoon commented 1 year ago

Register

where does register exist?

When you read from or write to these specific memory addresses, you are effectively accessing and modifying the internal peripheral registers of the STM32F411RE microcontroller. These registers are used to control and configure the behavior of various on-chip peripherals, such as GPIO, UART, timers, and many others.

hyunsik-yoon commented 1 year ago

Memory map

hyunsik-yoon commented 1 year ago

Peripheral examples

  1. I2C sensors and modules:

    • BMP280/BME280: Temperature, pressure, and humidity sensor
    • SSD1306 OLED display: Small monochrome display module
    • ADS1115: 16-bit ADC module
  2. SPI sensors and modules:

    • MAX31865: RTD-to-digital converter for temperature sensing
    • ILI9341: TFT LCD display module
    • MCP2515: CAN bus controller with SPI interface
  3. UART sensors and modules:

    • HC-05: Bluetooth module for wireless communication
    • SIM800L: GSM/GPRS module for cellular communication
    • GPS modules: For location and time information
hyunsik-yoon commented 1 year ago

point-to-point communication between two MCUs

  1. Point-to-point communication between two MCUs refers to a direct connection between two microcontrollers for data exchange, without the involvement of any intermediary devices. This can be achieved using various communication protocols, such as UART, I2C, SPI, or CAN. The choice of protocol depends on your application's requirements, such as data transfer speed, communication distance, and bus topology.

  2. Here's a step-by-step guide to set up point-to-point communication between two Arduino Uno boards using UART:

Step 1: Buy the necessary hardware

Step 2: Connect the hardware

Step 3: Prepare the software

You will need the Arduino IDE installed on your computer. If you don't have it, download and install it from https://www.arduino.cc/en/software.

Step 4: Write code for Arduino1 (UART Transmitter)

Create a new sketch in the Arduino IDE and enter the following code for Arduino1:

void setup() {
  Serial.begin(9600); // Initialize serial communication at 9600 baud
}

void loop() {
  Serial.println("Hello from Arduino1"); // Send a message
  delay(1000); // Wait for 1 second
}

Upload this code to Arduino1 using the Arduino IDE.

Step 5: Write code for Arduino2 (UART Receiver)

Create another new sketch in the Arduino IDE and enter the following code for Arduino2:

void setup() {
  Serial.begin(9600); // Initialize serial communication at 9600 baud
}

void loop() {
  if (Serial.available() > 0) { // Check if there's data available
    String receivedMessage = Serial.readStringUntil('\n'); // Read the incoming message
    Serial.println("Received: " + receivedMessage); // Print the received message
  }
}

Upload this code to Arduino2 using the Arduino IDE. Make sure to select the correct serial port for Arduino2 in the IDE.

Step 6: Test the communication

hyunsik-yoon commented 1 year ago

Multi-master configuration on I2C

define SCREEN_WIDTH 128

define SCREEN_HEIGHT 64

define OLED_RESET -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() { Wire.begin(); // Initialize I2C as master display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Initialize the OLED display display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0, 0); display.println("Master 1"); display.display(); delay(2000); // Wait for 2 seconds }

void loop() { // No code needed for this simple example }

hyunsik-yoon commented 1 year ago

performance trade-off 사례

  1. Low-power sensor node:
    • Trade-off: Power consumption vs. latency
    • GPS 센서를 sleep 시키다가 특정 wake-up interval 후 읽는 경우.
    • 배터리는 오래가나, 자주 못 읽으므로 latency 발생
  2. Real-time control system:
    • Trade-off: Latency vs. throughput
    • 모터 동작을 빨리 시키려고 이 task 에 high priority 를 주는 경우 다른 task 들은 상대적으로 throughput 에 제약을 받게 된다.
  3. High-speed data acquisition system:
    • Trade-off: Throughput vs. power consumption
    • DSP 처리 데이터처럼 많은 데이터를 자주 받아야 하는 경우, 당연히 power consumption이 늘어난다.
  4. Audio processing system:
    • Trade-off: Quality vs. computational complexity
    • audio 처리시 적은 데이터와 적은 power 로 처리하려면 audio quality가 떨어지게 된다.
  5. Wearable device:
    • Trade-off: Power consumption vs. functionality
    • wearable 시계의 화면을 계속 켜둘 것인가? 밝기는 어둡게 할 것인가? WiFi 도 지원할 것인가? 등등
hyunsik-yoon commented 1 year ago

UART vs SPI vs I2C

hyunsik-yoon commented 1 year ago

기타

#include "stm32f4xx_hal.h"

void SystemClock_Config(void);
void Error_Handler(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();

  // Initialize the watchdog timer
  IWDG_HandleTypeDef hiwdg;
  hiwdg.Instance = IWDG;
  hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
  hiwdg.Init.Reload = 4095;
  if (HAL_IWDG_Init(&hiwdg) != HAL_OK)   // <--------- init watchdog
  {
    Error_Handler();
  }

  while (1)
  {
    // Main loop of the application
    // ...

    HAL_IWDG_Refresh(&hiwdg);    // <-------- Reset the watchdog timer periodically
  }
}

void Error_Handler(void)
{
  // Handle errors here, e.g., blink an LED, log an error, or restart the system
}

volatile uint32_t task_timestamps[NUM_TASKS];

void task1(void) { while (1) { // Task1 code // ...

// Report task status
task_timestamps[0] = HAL_GetTick();

} }

void task2(void) { while (1) { // Task2 code // ...

// Report task status
task_timestamps[1] = HAL_GetTick();

} }

void task3(void) { while (1) { // Task3 code // ...

// Report task status
task_timestamps[2] = HAL_GetTick();

} }

void task_supervisor(void) { while (1) { // Check each task's timestamp for (int i = 0; i < NUM_TASKS; i++) { uint32_t current_time = HAL_GetTick(); if (current_time - task_timestamps[i] > TASK_TIMEOUT) // <-- refresh 안한애가 있나? { // Task has timed out, take appropriate action // ... } }

// Sleep or wait for a while before checking again
HAL_Delay(500);

} }

hyunsik-yoon commented 1 year ago

Logic analyzer intro

hyunsik-yoon commented 1 year ago

Tasks in Smart Thermostat System (on FreeRTOS)

  1. Temperature measurement task (medium priority): This task reads temperature data from a sensor periodically (e.g., every second). It's not very time-critical, but it should run regularly to keep the temperature data up-to-date.

  2. User interface task (high priority): This task handles user inputs, such as buttons or touch events, and updates the display. It needs to be responsive to provide a good user experience, so it should have a higher priority.

  3. HVAC control task (medium priority): This task implements the control logic for the heating, ventilation, and air conditioning (HVAC) system. It checks the current temperature and the desired temperature set by the user, and it controls the HVAC system accordingly. The control task needs to run periodically but doesn't have strict timing requirements.

  4. Communication task (low priority): This task handles communication with external devices or cloud services, such as sending temperature data or receiving control commands. Since communication can be slow and may involve retries, this task should have a lower priority to avoid blocking other tasks.

In summary, tasks should be designed based on the system requirements, and priorities should be assigned according to the responsiveness and importance of each task.

Smart Door Lock System

  1. Keypad input task (high priority): This task handles user inputs from a keypad or touch panel. It needs to be responsive to provide a good user experience and quickly detect valid or invalid input. Therefore, it should have a high priority.

  2. RFID or NFC reader task (high priority): If the system supports RFID or NFC cards for access control, this task reads data from the RFID or NFC reader and processes the card information. It should have a high priority to quickly detect and process card data, ensuring fast and seamless access for authorized users.

  3. Lock control task (medium priority): This task controls the door lock's motor or solenoid based on the input from the keypad or RFID/NFC reader. It checks the input data against the stored access codes or user database, and it either grants or denies access by actuating the lock. While this task is important, it does not need to be as responsive as the input tasks, so it can have a medium priority.

  4. Wireless communication task (low priority): If the smart door lock is connected to a home automation system or cloud service, this task handles wireless communication, such as Wi-Fi or Bluetooth, to send and receive data or commands. This task can have a lower priority since communication may involve delays or retries, and it should not block other time-critical tasks.

  5. Battery monitoring task (low priority): This task monitors the battery level of the smart door lock and generates alerts or notifications when the battery is low. Since this task does not need to be very responsive and can run periodically, it can have a low priority.