Open hyunsik-yoon opened 1 year ago
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:
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.
// Driver API (header file)
void EXTIx_IRQHandler(void); // Interrupt handler for EXTI line x
void button_init(void); // Initializes button and configures the interrupt
baremetal: host
// interrupt vector table 에 지정된 handler
void EXTIx_IRQHandler(void) {
// Clear the interrupt flag
EXTI->PR |= (1 << x);
// Handle the button press event
// Your code here
}
- 참고: embedded linux UART 접근은 파일 접근으로 사용
```c
uart_fd = uart_init("/dev/ttyS0", B115200);
bytes_read = read(uart_fd, &rx_data, sizeof(rx_data));
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.
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.
arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -Og -Wall source_file.c -o output_file.o
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Og -Wall -T linker_script.ld -o output_file.elf input_file1.o input_file2.o
arm-none-eabi-objcopy -O ihex output_file.elf output_file.hex
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
startup_stm32f411retx.s
fileused in baremetal
an assembly language source file that contains the startup code for the STM32F411RE microcontroller. This file is specific to the STM32F411RE series of microcontrollers, where "Tx" in the file name denotes the package type (e.g., LQFP64, LQFP100, etc.) and the number of pins available on the microcontroller.
The startup code is responsible for initializing critical parts of the microcontroller and setting up the environment before executing the main application code. Some tasks performed by the startup code include:
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.
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.
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.
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.
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.
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.
힙업 스택다운
(초기화도 안해놨니?? 불쉿..... BS...S...) 이렇게 외워 ㅎㅎ
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.
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.
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.
STM32F411 case
among memory for bus, what's in APB2?
I2C sensors and modules:
SPI sensors and modules:
UART sensors and modules:
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.
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
e.g., 2 masters (STM32 Nucleo boards) + 1 slave (OLED display or temperature sensor)
can be complex due to the need for arbitration and collision detection mechanisms to ensure that two masters do not attempt to communicate simultaneously
master 1 이 display init 하는 코드 (arbitration 등은 나타나있지 않)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
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 }
#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
}
#define NUM_TASKS 3
#define TASK_TIMEOUT 1000
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);
} }
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.
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.
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.
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.
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.
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.
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.
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.
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.
let's study.