Merge pull request #5669 from mysterywolf/doc

This commit is contained in:
guo 2022-03-14 18:57:52 +08:00 committed by GitHub
commit f26099d838
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
255 changed files with 21076 additions and 95 deletions

136
documentation/README.md Normal file
View File

@ -0,0 +1,136 @@
# RT-Thread
RT-Thread (Real-Time Thread) is an open source embedded real-time operating system and released under Apache License v2.0. It has a strong scalability: from a nano kernel running on a tiny MCU, for example ARM Cortex-M0, or Cortex-M3/4/7, to a rich feature system running on MIPS32, ARM Cortex-A, even the emerging open source RISC-V architecture is supported. RT-Thread can run either on single-core systems or on symmetric multi-core processors(SMP) systems.
## Introduction
RT-Thread has not noly a real-time kernel, but also rich components. Its architecture is as follows:
![RT-Thread system framework](figures/02Software_framework_diagram.png)
- **Kernel**: It includes preemptive multi-task real-time scheduler, and infrastructure such as semaphore, mutex, mailbox, message queue, signal, event, memory management, timer management, interrupt management, etc. It also includes libcpu/BSP (file related to chip transplantation/board support package).
- **components**: It is a software unit on the RT-Thread kernel layer, such as command line (FinSH), device driver framework (Device Drivers), network framework, virtual file system (FAT, YAFFS, UFFS, ROM/RAM file system, etc.), TCP/IP network protocol stack (lwIP), libc/POSIX standard layer and so on. Generally, a software component is placed in a folder in the RT-Thread/components directory, and each software component is described by a *SConscript* file and added to the RT-Thread construction system. When a software component is opened in the system configuration, it will be compiled and linked to the final RT-Thread firmware.
- **Packages**: It is a middleware running on RT-Thread IoT operating system platform and facing different application fields. Packages consist of description information, source code or library files. These packages can be provided by RT-Thread, third-party developers and enterprises, and the license agreement of the packages is provided by the author of the packages. These software packages have strong reusability and high modularity, which greatly facilitates application developers to build their desired application systems in the shortest time. For more package information, visit the [RT-Thread package repository](https://github.com/RT-Thread-packages).
## Licence
RT-Thread is an open source software and has been licensed under Apache License Version 2.0 since v3.1.1. License information and copyright information can generally be seen at the beginning of the code:
```
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
```
To avoid possible future license conflicts, developers need to sign a Contributor License Agreement (CLA) when submitting PR to RT-Thread.
> Note: Because the BSP also contains the code provided by the chip manufacturer, this part of the code follows the license provided by the chip manufacturer, such as STM32 HAL, NXP, Atmel, etc. Relevant codes are usually only used in the chips of the corresponding manufacturers.
## Supported Architectures
RT-Thread RTOS can support many architectures,and has covered the major architectures in current applications. Architecture and chip manufacturer involved:
- **ARM Cortex-M0/M0+**manufacturers like ST
- **ARM Cortex-M3**manufacturers like ST、Winner Micro、MindMotion, ect.
- **ARM Cortex-M4**manufacturers like ST、Nuvton、NXP、GigaDevice、Realtek、Ambiq Micro, ect.
- **ARM Cortex-M7**manufacturers like ST、NXP
- **ARM Cortex-M23**manufacturers like GigaDevice
- **ARM Cortex-R4**
- **ARM Cortex-A8/A9**manufacturers like NXP
- **ARM7**manufacturers like Samsung
- **ARM9**manufacturers like Allwinner、Xilinx 、GOKE
- **ARM11**manufacturers like Fullhan
- **MIPS32**manufacturers like loongson、Ingenic
- **RISC-V**manufacturers like Hifive、Kendryte
- **ARC**manufacturers like SYNOPSYS
- **DSP**manufacturers like TI
- **C-Sky**
- **x86**
## Supported IDE and Compiler
The main IDE/compilers supported by RT-Thread are:
- MDK KEIL
- IAR
- GCC
Use Python-based [scons](http://www.scons.org) for command-line builds.
# Source Code and Tools
**Get the source code**: The source code of RT-Thread is hosted on Github, and click on the link to get the source code.
- [Download RT-Thread source code](https://github.com/RT-Thread/rt-thread)
**Get the Env Tool**: To better help developers, the RT-Thread team also provides Env tools (or Env scripts for Linux/MacOS). On Windows, Env tool is a development assistant tool launched by RT-Thread. It provides compiling and building environment, graphical system configuration and software package management functions for project projects based on RT-Thread operating system. Its built-in menuconfig provides a simple and easy-to-use configuration tailoring tool, which can tailor the kernel, components and software packages freely, so that the system can be built in the way of building blocks.
- [Download Env Tool]()
- [User manual of Env](env/env.md)
# Getting Started
RT-Thread BSP can be compiled directly and downloaded to the corresponding development board for use. In addition, RT-Thread also provides qemu-vexpress-a9 BSP, which can be used without hardware platform. See the getting started guide below for details.
- [Getting Started of QEMU (Windows)](quick-start/quick_start_qemu/quick_start_qemu.md)
- [Getting Started of QEMU (Ubuntu)](quick-start/quick_start_qemu/quick_start_qemu_linux.md)
# Help
Any questions can be asked in the [issue section of rtthread-manual-doc](https://github.com/RT-Thread/rtthread-manual-doc/issues). By creating a new issue to describe your questions, community members will answer them.
# Contribution
If you are interested in RT-Thread and want to join in the development of RT-Thread and become a code contributor,please refer to the [Code Contribution Guide](documentation/contribution_guide/contribution_guide.md).
# Manual Catalogue
- [RT-Thread Introduction](introduction/introduction.md)
- [Start Guide: Simulate STM32F103 on KEIL simulator](quick-start/quick-start.md)
**Kernel**
- [Kernel Basics](basic/basic.md)
- [Thread Management](thread/thread.md)
- [Clock&Timer Management](timer/timer.md)
- [Inter-thread Synchronization](thread-sync/thread-sync.md)
- [Inter-thread Communication](thread-comm/thread-comm.md)
- [Memory Management](memory/memory.md)
- [Interrupt Management](interrupt/interrupt.md)
- [Kernel Porting](kernel-porting/kernel-porting.md)
**Tool**
- [User Manual of Env](env/env.md)
- [SCons](scons/scons.md)
**Device**
- [I/O Device Framework](device/device.md)
- [PIN Device](device/pin/pin.md)
- [UART Device](device/uart/uart.md)
- [ADC Device](device/adc/adc.md)
- [I2C Bus Device](device/i2c/i2c.md)
- [SPI Device](device/spi/spi.md)
- [PWM Device](device/pwm/pwm.md)
- [RTC Device](device/rtc/rtc.md)
- [HWTIMER Device](device/hwtimer/hwtimer.md)
- [WATCHDOG Device](device/watchdog/watchdog.md)
- [WLAN Device](device/wlan/wlan.md)
- [Sensor Device](device/sensor/sensor.md)
**Components**
- [FinSH Console](finsh/finsh.md)
- [Virtual File System](filesystem/README.md)
- [utest Framework](utest/utest.md)
- [Dynamic Module: dlmodule](dlmodule/README.md)
- [Socket Abstraction Layer: SAL](sal/sal.md)
- [AT Commands](at/at.md)
- [POSIX Interface](posix/README.md)
- [Ulog Log](ulog/ulog.md)
- [Power Management: PM](pm/pm.md)
- [Network Framework](network/network.md)

895
documentation/at/at.md Normal file
View File

@ -0,0 +1,895 @@
# AT Commands #
## Introduction to AT Commands
AT Commands was originally a control protocol invented by Hayes, which invented MODEM, to control MODEM. Later, with the upgrade of network bandwidth, the dial-up MODEM with very low speed basically exited the general market, but the AT command was retained. At that time, the major mobile phone manufacturers jointly developed a set of AT commands for GSM to control the GSM module of the mobile phone. The AT command evolved on this basis and added the GSM 07.05 standard and the later GSM 07.07 standard to achieve a more robust standardization.
In the subsequent GPRS control, 3G module, etc., all use AT commands to control, AT commands gradually become the actual standard in product development. Nowadays, AT commands are also widely used in embedded development. AT commands are the protocol interfaces of the main chip and communication module. The hardware interface is usually the serial port, so the main control device can complete various operations through simple commands and hardware design.
**The AT commands is a way of applying device connections and data communication between the AT Server and the AT Client.** The basic structure is shown below:
![AT Command Set](figures/at_framework.jpg)
1. The AT command consists of three parts: prefix, body, and terminator. The prefix consists of the character AT; the body consists of commands, parameters, and possibly used data; the terminator typically is `<CR><LF>` (`"\r\n"`).
2. The implementation of the AT function requires the AT Server and the AT Client to work together.
3. The AT server is mainly used to receive commands sent by the AT client, determine the received commands and parameter formats, and deliver corresponding response data or actively send data.
4. The AT client is mainly used to send commands, wait for the AT Server to respond, and parse the AT Server response data or the actively sent data to obtain related information.
5. A variety of data communication methods (UART, SPI, etc.) are supported between AT Server and AT Client. Currently, the most commonly used serial port UART communication method.
6. The data that the AT Server sends to the AT Client is divided into two types: response data and URC data.
- Response Data: The AT Server response status and information received by the AT Client after sending the command.
- URC Data: The data that the AT Server actively sends to the AT client generally appears in some special cases, such as disconnected WIFI connection, TCP receiving data, etc. These situations often require the user to perform corresponding operations.
With the popularization of AT commands, more and more embedded products use AT commands. The AT commands are used as the protocol interfaces of the main chip and the communication module. The hardware interface is generally a serial port, so that the master device can performs a variety of operations using simple commands and hardware design.
Although the AT command has standardization to a certain degree, the AT commands supported by different chips are not completely unified, which directly increases the complexity to use. There is no uniform way to handle the sending and receiving of AT commands and the parsing of data. Moreover, when the AT device is used to connect to the network, the simple device connection and data transceiving functions can only be completed by commands, and it is difficult to adapt the upper layer network application interface, which is not conducive to the development of the product device.
In order to facilitate the user to use AT commands to easily adapt to different AT modules, RT-Thread provides AT components for AT device connectivity and data communication. The implementation of the AT component consists of both client and server.
## Introduction to AT Components
The AT component is based on the implementation of the `AT Server` and `AT Client` of the RT-Thread system. The component completes the AT command transmission, command format and parameter judgment, command response, response data reception, response data parsing, URC data processing, etc.. Command data interaction process.
Through the AT component, the device can use the serial port to connect to other devices to send and receive parsed data. It can be used as an AT Server to allow other devices or even the computer to connect to complete the response of sending data. It can also start the CLI mode in the local shell to enable the device to support AT Server and AT Client at the same time. Server and AT Client features, this mode is mostly used for device development and debugging.
**AT component resource usage:**
- AT Client: 4.6K ROM and 2.0K RAM
- AT Server: 4.0K ROM and 2.5K RAM
- AT CLI: 1.5K ROM and almost no RAM is used.
Overall, the AT component resources are extremely small, making them ideal for use in embedded devices with limited resources. The AT component code is primarily located in `rt-thread/components/net/at/`. The main functions includes :
**Main Functions of AT Server:**
- Basic commands: Implement a variety of common basic commands (ATE, ATZ, etc.);
- Command compatibility: The command supports ignoring case and improving command compatibility;
- Command detection: The command supports custom parameter expressions and implements self-detection of received command parameters.
- Command registration: Provides a simple way to add user-defined commands, similar to the way the `finsh/msh` command is added;
- Debug mode: Provides AT Server CLI command line interaction mode, mainly used for device debugging.
**Main Functions of AT Client:**
- URC data processing: The complete URC data processing method;
- Data analysis: Supports the analysis of custom response data, and facilitates the acquisition of relevant information in the response data;
- Debug mode: Provides AT Client CLI command line interaction mode, mainly used for device debugging.
- AT Socket: As an extension of AT Client function, it uses AT command to send and receive as the basis, implements the standard BSD Socket API, completes the data sending and receiving function, and enables users to complete device networking and data communication through AT commands.
- Multi-client support: The AT component currently supports multiple clients running simultaneously.
## AT Server ##
### AT Server Configuration ###
When we use the AT Server feature in the AT component, we need to define the following configuration in rtconfig.h:
| **Macro Definition** | **Description** |
| ---- | ---- |
|RT_USING_AT| Enable AT component |
|AT_USING_SERVER |Enable AT Server function|
|AT_SERVER_DEVICE |Define the serial communication device name used by AT Server on the device to ensure that it is not used and the device name is unique, such as `uart3` device.|
|AT_SERVER_RECV_BUFF_LEN|The maximum length of data received by the AT Server device|
|AT_CMD_END_MARK_CRLF|Determine the line terminator of the received command |
|AT_USING_CLI | Enable server-command-line interaction mode |
|AT_DEBUG|Enable AT component DEBUG mode to display more debug log information |
|AT_PRINT_RAW_CMD | Enable real-time display AT command communication data mode for easy debugging |
For different AT devices, there are several formats of the line terminator of the sending commands: `"\r\n"`、`"\r"`、`"\n"`, the user needs to select the corresponding line terminator according to the device type connected to the AT Server. And then determine the end of the send command line, defined as follows:
| **Macro Definition** | **Terminator** |
| ---- | ---- |
| AT_CMD_END_MARK_CRLF | `"\r\n"` |
| AT_CMD_END_MARK_CR | `"\r"` |
| AT_CMD_END_MARK_LF | `"\n"` |
The above configuration options can be added by Env tool. The specific path in Env is as follows:
```c
RT-Thread Components --->
Network --->
AT commands --->
[*] Enable AT commands
[*] Enable debug log output
[*] Enable AT commands server
(uart3) Server device name
(256) The maximum length of server data accepted
The commands new line sign (\r\n) --->
[ ] Enable AT commands client
[*] Enable command-line interface for AT commands
[ ] Enable print RAW format AT command communication data
```
After the add configuration is complete, you can use the command line to rebuild the project, or use `scons` to compile.
### AT Server Initialization ###
After enabling the AT Server in Env, you need to initialize it at startup aims to enable the AT Server function. If the component has been initialized automatically, no additional initialization is required. Otherwise, you need to call the following function in the initialization task. :
```c
int at_server_init(void);
```
The AT Server initialization function, which belongs to the application layer function, needs to be called before using the AT Server function or using the AT Server CLI function. `at_server_init()` function completes initialization of resources stored by AT commands ,such as data segment initialization, AT Server device initialization, and semaphore usage by the AT Server, and creates an at_server thread for parsing the receipt data in the AT Server.
After the AT Server is successfully initialized, the device can be used as an AT server to connect to the AT client's serial device for data communication, or use a serial port conversion tool to connect to the PC, so that the PC-side serial debugging assistant can communicate with the AT client as data communication.
### Add custom AT commands ###
At present, the format of the AT command set used by AT devices of different manufacturers does not have a completely uniform standard, so the AT Server in the AT component only supports some basic general AT commands, such as ATE, AT+RST, etc. These commands can only be used to meet the basic operation of the device. If users want to use more functions, they need to implement custom AT Server commands for different AT devices.AT component provides AT command addition method similar to finsh/msh command addition method, which is convenient for users to implement the required commands.
The basic commands currently supported by AT Server are as follows:
- AT: AT test command;
- ATZ: The device is restored to factory settings;
- AT+RST: Reboot device ;
- ATE: ATE1 turns on echo, ATE0 turns off echo;
- AT&L: List all commands;
- AT+UART: Set the serial port information.
AT commands can implement different functions depending on the format of the incoming parameters. For each AT command, there are up to four functions, as described below:
- Test Function: `AT+<x>=?` , used to query the command's parameter, format and value range;
- Query Function: `AT+<x>?`, used to return the current value of the command parameter;
- Setting Function: `AT+<x>=...` , used for user-defined parameter values;
- Execution Function: `AT+<x>`, used to perform related operations.
The four functions of each command do not need to be fully implemented. When you add the AT Server command, you can implement one or several of the above functions according to your needs. Unimplemented functions can be represented by `NULL` . And then through custom commands, the add function is added to the list of basic commands. The addition method is similar to the way the `finsh/msh` command is added. The function for adding commands is as follows:
```c
AT_CMD_EXPORT(_name_, _args_expr_, _test_, _query_, _setup_, _exec_);
```
|**Parameter** |**Description** |
| ---------- | ------------------------------- |
| `_name_ ` | AT command name |
| `_args_expr_` | AT command parameter expression; (NULL means no parameter, `<>` means mandatory parameter and `[]` means optional parameter |
| `_test_` | AT test function name; (NULL means no parameter) |
| `_query_` | AT query function name; (ibid.) |
| `_setup_` | AT setup function name; (ibid.) |
| `_exec_` | AT performs the function name; (ibid.) |
The AT command registration example is as follows. The `AT+TEST` command has two parameters. The first parameter is a mandatory parameter, and the second parameter is an optional parameter. The command implements the query function and the execution function:
```c
static at_result_t at_test_exec(void)
{
at_server_printfln("AT test commands execute!");
return 0;
}
static at_result_t at_test_query(void)
{
at_server_printfln("AT+TEST=1,2");
return 0;
}
AT_CMD_EXPORT("AT+TEST", =<value1>[,<value2>], NULL, at_test_query, NULL, at_test_exec);
```
### AT Server APIs
#### Send Data to the Client (no newline)
```c
void at_server_printf(const char *format, ...);
```
This function is used by the AT Server to send fixed-format data to the corresponding AT Client serial device through the serial device. The data ends without a line break. Used to customize the function functions of AT commands in AT Server.
| **Parameter** | **D**escription |
|------|-------------------------|
| format | Customize the expression of the input data |
| ... | Input data list, variable parameters |
#### Send Data to the Client (newline)
```c
void at_server_printfln(const char *format, ...);
```
This function is used by the AT Server to send fixed-format data to the corresponding AT Client serial device through the serial device, with a newline at the end of the data. Used to customize the function functions of AT commands in AT Server.
| **Parameter** | **Description** |
|------|-------------------------|
| format | Customize the expression of the input data |
| ... | Input data list, variable parameters |
#### Send Command Execution Results to the Client
```c
void at_server_print_result(at_result_t result);
```
This function is used by the AT Server to send command execution results to the corresponding AT Client serial device through the serial device. The AT component provides a variety of fixed command execution result types. When you customize a command, you can use the function to return the result directly;
| **Parameter** | **Description** |
|------|-----------------|
| result | Command execution result type |
The command execution result type in the AT component is given in the enumerated type, as shown in the following table:
| Types of Command Execution Result | Description |
|------------------------|------------------|
| AT_RESULT_OK | Command Execution Succeeded |
| AT_RESULT_FAILE | Command Execution Failed |
| AT_RESULT_NULL | Command No Result |
| AT_RESULT_CMD_ERR | Command Input Error |
| AT_RESULT_CHECK_FAILE | Parameter Expression Matching Error |
| AT_RESULT_PARSE_FAILE | Parameter Parsing Error |
See the following code to learn how to use the `at_server_print_result` function:
```c
static at_result_t at_test_setup(const char *args)
{
if(!args)
{
/* If the parameter error after incoming orders, returns expression match error results */
at_server_print_result(AT_RESULT_CHECK_FAILE);
}
/* Return to successful execution under normal conditions */
at_server_print_result(AT_RESULT_OK);
return 0;
}
static at_result_t at_test_exec(void)
{
// execute some functions of the AT command.
/* This command does not need to return results */
at_server_print_result(AT_RESULT_NULL);
return 0;
}
AT_CMD_EXPORT("AT+TEST", =<value1>,<value2>, NULL, NULL, at_test_setup, at_test_exec);
```
#### Parsing Input Command Parameters
```c
int at_req_parse_args(const char *req_args, const char *req_expr, ...);
```
Parsing input command parameters Among the four function functions of an AT command, only the setting function has an input parameter, and the input parameter is to remove the rest of the AT command, for example, a command input is `"AT+TEST=1,2,3,4"`, Then set the input parameter of the function to the parameter string `"=1,2,3,4"` .
The command parsing function is mainly used in the AT function setting function, which is used to parse the incoming string parameter and obtain corresponding multiple input parameters for performing the following operations. The standard `sscanf` parsing grammar used in parsing grammar here will also be described in detail later in the AT Client parameter parsing function.
| **Parameter** | **Description** |
|---------|-----------------------------------------------|
| req_args | The incoming parameter string of the request command |
| req_expr | Custom parameter parsing expression for parsing the above incoming parameter data |
| ... | Output parsing parameter list, which is a variable parameter |
| **Return** | -- |
| >0 | Successful, returns the number of variable parameters matching the parameter expression |
| =0 | Failed, no parameters matching the parameter expression |
| -1 | Failed, parameter parsing error |
See the following code to learn how to use the at_server_print_result function:
```c
static at_result_t at_test_setup(const char *args)
{
int value1,value2;
/* The input standard format of args should be "=1, 2", "=%d, %d" is a custom parameter parsing expression, and the result is parsed and stored in the value1 and value2 variables. */
if (at_req_parse_args(args, "=%d,%d", &value1, &value2) > 0)
{
/* Data analysis succeeds, echoing data to AT Server serial device */
at_server_printfln("value1 : %d, value2 : %d", value1, value2);
/* The data is parsed successfully. The number of parsing parameters is greater than zero. The execution is successful. */
at_server_print_result(AT_RESULT_OK);
}
else
{
/* Data parsing failed, the number of parsing parameters is not greater than zero, and the parsing failure result type is returned. */
at_server_print_result(AT_RESULT_PARSE_FAILE);
}
return 0;
}
/* Add the "AT+TEST" command to the AT command list. The command parameters are formatted as two mandatory parameters <value1> and <value2>. */
AT_CMD_EXPORT("AT+TEST", =<value1>,<value2>, NULL, NULL, at_test_setup, NULL);
```
#### Portation-related interfaces
AT Server supports a variety of basic commands (ATE, ATZ, etc.) by default. The function implementation of some commands is related to hardware or platform and requires user-defined implementation. The AT component source code `src/at_server.c` file gives the weak function definition of the migration file. The user can create a new migration file in the project to implement the following function to complete the migration interface, or modify the weak function to complete the migration interface directly in the file.
1. Device restart function: `void at_port_reset(void);`. This function completes the device soft restart function and is used to implement the basic command AT+RST in AT Server.
2. The device restores the factory settings function: `void at_port_factory_reset(void);`. This function completes the device factory reset function and is used to implement the basic command ATZ in AT Server.
3. Add a command table in the link script (add only in gcc, no need to add in keil and iar)
If you use the gcc toolchain in your project, you need to add the *section* corresponding to the AT server command table in the link script. Refer to the following link script:
```c
/* Constant data goes into FLASH */
.rodata :
{
...
/* section information for RT-thread AT package */
. = ALIGN(4);
__rtatcmdtab_start = .;
KEEP(*(RtAtCmdTab))
__rtatcmdtab_end = .;
. = ALIGN(4);
} > CODE
```
## AT Client
### AT Client Configuration
When we use the AT Client feature in the AT component, we need to define the following configuration in rtconfig.h:
```c
#define RT_USING_AT
#define AT_USING_CLIENT
#define AT_CLIENT_NUM_MAX 1
#define AT_USING_SOCKET
#define AT_USING_CLI
#define AT_PRINT_RAW_CMD
```
- `RT_USING_AT`: Used to enable or disable the AT component;
- `AT_USING_CLIENT`: Used to enable the AT Client function;
- `AT_CLIENT_NUM_MAX`: Maximum number of AT clients supported at the same time.
- `AT_USING_SOCKET`: Used by the AT client to support the standard BSD Socket API and enable the AT Socket function.
- `AT_USING_CLI`: Used to enable or disable the client command line interaction mode.
- `AT_PRINT_RAW_CMD`: Used to enable the real-time display mode of AT command communication data for easy debugging.
The above configuration options can be added directly to the `rtconfig.h` file or added by the Env. The specific path in Env is as follows:
```c
RT-Thread Components --->
Network --->
AT commands --->
[*] Enable AT commands
[ ] Enable debug log output
[ ] Enable AT commands server
[*] Enable AT commands client
(1) The maximum number of supported clients
[*] Enable BSD Socket API support by AT commnads
[*] Enable command-line interface for AT commands
[ ] Enable print RAW format AT command communication data
```
After the configuration is complete, you can use the command line to rebuild the project, or use `scons` to compile.
### AT Client Initialization ###
After configuring the AT Client, you need to initialize it at startup aims to enable the AT client function. If the component has been initialized automatically, no additional initialization is required. Otherwise, you need to call the following function in the initialization task:
```c
int at_client_init(const char *dev_name, rt_size_t recv_bufsz);
```
The AT Client initialization function, which belongs to the application layer function, needs to be called before using the AT Client function or using the AT Client CLI function. The `at_client_init()` function completes the initialization of the AT Client device, the initialization of the AT Client porting function, the semaphore and mutex used by the AT Client, and other resources, and creates the `at_client` thread for parsing the data received in the AT Client and for processing the URC data.
### AT Client data receiving and sending ###
The main function of the AT Client is to send AT commands, receive data, and parse data. The following is an introduction to the processes and APIs related to AT Client data reception and transmission.
Related structure definition:
```c
struct at_response
{
/* response buffer */
char *buf;
/* the maximum response buffer size */
rt_size_t buf_size;
/* the number of setting response lines
* == 0: the response data will auto return when received 'OK' or 'ERROR'
* != 0: the response data will return when received setting lines number data */
rt_size_t line_num;
/* the count of received response lines */
rt_size_t line_counts;
/* the maximum response time */
rt_int32_t timeout;
};
typedef struct at_response *at_response_t;
```
In the AT component, this structure is used to define a control block for AT command response data, which is used to store or limit the data format of the AT command response data.
- `buf` is used to store the received response data. Note that the data stored in the buf is not the original response data, but the data of the original response data removal terminator (`"\r\n"`). Each row of data in the buf is split by '\0' to make it easy to get data by row.
- `buf_size` is a user-defined length of the received data that is most supported by this response. The length of the return value is defined by the user according to his own command.
- `line_num` is the number of rows that the user-defined response data needs to receive. **If there is no response line limit requirement, it can be set to 0.**
- `line_counts` is used to record the total number of rows of this response data.
- `timeout` is the user-defined maximum response time for this response data.
`buf_size`、`line_num`、`timeout` parameters in the structure are restricted conditions, which are set when the structure is created, and other parameters are used to store data parameters for later data analysis.
Introduction to related API interfaces:
#### Create a Response Structure
```c
at_response_t at_create_resp(rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout);
```
| **Parameter** | **Description** |
|---------|-----------------------------------------|
| buf_size | Maximum length of received data supported by this response |
| line_num | This response requires the number of rows of data to be returned. The number of rows is divided by a standard terminator (such as "\r\n"). If it is 0, it will end the response reception after receiving the "OK" or "ERROR" data; if it is greater than 0, it will return successfully after receiving the data of the current set line number. |
| timeout | The maximum response time of the response data, receiving data timeout will return an error |
| **Return** | -- |
| != NULL | Successful, return a pointer to the response structure |
| = NULL | Failed, insufficient memory |
This function is used to create a custom response data receiving structure for later receiving and parsing the send command response data.
#### Delete a Response Structure
```c
void at_delete_resp(at_response_t resp);
```
| **Parameter** | **Description** |
|----|-------------------------|
| resp | The response structure pointer to be deleted |
This function is used to delete the created response structure object, which is generally paired with the **at_create_resp** creation function.
#### Set the Parameters of Response Structure
```c
at_response_t at_resp_set_info(at_response_t resp, rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout);
```
| **Parameter** | **Description** |
|---------|----------------------------------|
| resp | Response structure pointer that has been created |
| buf_size | Maximum length of received data supported by this response |
| line_num | This response requires the number of rows of data to be returned. The number of lines is divided by the standard terminator. If it is 0, the response is received after receiving the "OK" or "ERROR" data. If it is greater than 0, the data is successfully returned after receiving the data of the currently set line number. |
| timeout | The maximum response time of the response data, receiving data timeout will return an error. |
| **Return** | -- |
| != NULL | Successful, return a pointer to the response structure |
| = NULL | Failed, insufficient memory |
This function is used to set the response structure information that has been created. It mainly sets the restriction information on the response data. It is generally used after creating the structure and before sending the AT command. This function is mainly used to send commands when the device is initialized, which can reduce the number of times the response structure is created and reduce the code resource occupation.
#### Send a Command and Receive a Response
```c
rt_err_t at_exec_cmd(at_response_t resp, const char *cmd_expr, ...);
```
| **Parameter** | **Description** |
|---------|-----------------------------|
| resp | Response structure body pointer created |
| cmd_expr | Customize the expression of the input command |
| ... | Enter the command data list, a variable parameter |
| **Return** | -- |
| >=0 | Successful |
| -1 | Failed |
| -2 | Failed, receive response timed out |
This function is used by the AT Client to send commands to the AT Server and wait for a response. `resp` is a pointer to the response structure that has been created. The AT command uses the variable argument input of the match expression. **You do not need to add a command terminator at the end of the input command.**
Refer to the following code to learn how to use the above AT commands to send and receive related functions:
```c
/*
* Program listing: AT Client sends commands and receives response routines
*/
#include <rtthread.h>
#include <at.h> /* AT component header file */
int at_client_send(int argc, char**argv)
{
at_response_t resp = RT_NULL;
if (argc != 2)
{
LOG_E("at_cli_send [command] - AT client send commands to AT server.");
return -RT_ERROR;
}
/* Create a response structure, set the maximum support response data length to 512 bytes, the number of response data lines is unlimited, and the timeout period is 5 seconds. */
resp = at_create_resp(512, 0, rt_tick_from_millisecond(5000));
if (!resp)
{
LOG_E("No memory for response structure!");
return -RT_ENOMEM;
}
/* Send AT commands and receive AT Server response data, data and information stored in the resp structure */
if (at_exec_cmd(resp, argv[1]) != RT_EOK)
{
LOG_E("AT client send commands failed, response error or timeout !");
return -ET_ERROR;
}
/* Command sent successful */
LOG_D("AT Client send commands to AT Server success!");
/* Delete response structure */
at_delete_resp(resp);
return RT_EOK;
}
#ifdef FINSH_USING_MSH
#include <finsh.h>
/* Output at_Client_send to msh */
MSH_CMD_EXPORT(at_Client_send, AT Client send commands to AT Server and get response data);
#endif
```
The implementation principle of sending and receiving data is relatively simple. It mainly reads and writes the serial port device bound by the AT client, and sets the relevant number of rows and timeout to limit the response data. It is worth noting that the `res` response needs to be created first. The structure passed `in_exec_cmd` function for data reception. When the `at_exec_cmd` function's parameter `resp` is NULL, it means that the data sent this time **does not consider processing the response data and directly returns the result**.
### AT Client Data Parsing Method ###
After the data is normally acquired, the response data needs to be parsed, which is one of the important functions of the AT Client. Parsing of data in the AT Client provides a parsed form of a custom parsing expression whose parsing syntax uses the standard `sscanf` parsing syntax. Developers can use the custom data parsing expression to respond to useful information in the data, provided that the developer needs to review the relevant manual in advance to understand the basic format of the AT Server device response data that the AT Client connects to. The following is a simple AT Client data parsing method through several functions and routines.
#### Get Response Data for the Specified Line Number
```c
const char *at_resp_get_line(at_response_t resp, rt_size_t resp_line);
```
| **Parameter** | **Description** |
|----------|-----------------------------|
| resp |Response structure pointer |
| resp_line | Required line number for obtaining data |
| **Return** | -- |
| != NULL | Successful, return a pointer to the corresponding line number data |
| = NULL | Failed, input line number error |
This function is used to get a row of data with the specified line number in the AT Server response data. The line number is judged by the standard data terminator. The above send and receive functions `at_exec_cmd` have recorded and processed the data and line numbers of the response data in the `resp` response structure, where the data information of the corresponding line number can be directly obtained.
#### Get Response Data by the Specified Keyword
```c
const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword);
```
| **Parameter** | **Description** |
|-------|-----------------------------|
| resp |Response structure pointer |
| keyword | Keyword information |
| **Return** | -- |
| != NULL | Successful, return a pointer to the corresponding line number data |
| = NULL | Failed, no keyword information found |
This function is used to get a corresponding row of data by keyword in the AT Server response data.
#### Parse Response Data for the Specified Line Number
```c
int at_resp_parse_line_args(at_response_t resp, rt_size_t resp_line, const char *resp_expr, ...);
```
| **Parameter** | **Description** |
|----------|---------------------------------|
| resp |Response structure pointer |
| resp_line | Parsed data line number required, **from the start line number count 1** |
| resp_expr | Custom parameter parsing expression |
| ... | Parsing the parameter list as a variable parameter |
| **Return** | -- |
| >0 | Successful, return the number of parameters successfully parsed |
| =0 | Failed, no parameters matching the parsing expression |
| -1 | Failed, parameter parsing error |
This function is used to get a row of data with the specified line number in the AT Server response data, and parse the parameters in the row data.
#### Parse Response Data for a Row with Specified Keyword
```c
int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...);
```
| **Parameter** | **Description** |
|----------|---------------------------------|
| resp |Response structure pointer |
| keyword | Keyword information |
| resp_expr | Custom parameter parsing expression |
| ... | Parsing the parameter list as a variable parameter |
| **Return** | -- |
| >0 | Successful, return the number of parameters successfully parsed |
| =0 | Failed, no parameters matching the parsing expression |
| -1 | Failed, parameter parsing error |
This function is used to get a row of data containing a keyword in the AT Server response data and parse the parameters in the row data.
The data parsing syntax uses the standard `sscanf` syntax, the content of the syntax is more, developers can search their parsing syntax, here two procedures are used to introduce the simple use method.
#### Serial Port Configuration Information Analysis Example
The data sent by the client:
```c
AT+UART?
```
The response data obtained by the client:
```c
UART=115200,8,1,0,0\r\n
OK\r\n
```
The pseudo code is parsed as follows:
```c
/* Create a server response structure, the maximum length of user-defined receive data is 64 */
resp = at_create_resp(64, 0, rt_tick_from_millisecond(5000));
/* Send data to the server and receive response data in the resp structure */
at_exec_cmd(resp, "AT+UART?");
/* Analyze the serial port configuration information, 1 means parsing the first line of response data, '%*[^=]' means ignoring the data before the equal sign */
at_resp_parse_line_args(resp, 1,"%*[^=]=%d,%d,%d,%d,%d", &baudrate, &databits, &stopbits, &parity, &control);
printf("baudrate=%d, databits=%d, stopbits=%d, parity=%d, control=%d\n",
baudrate, databits, stopbits, parity, control);
/* Delete server response structure */
at_delete_resp(resp);
```
#### IP and MAC Address Resolution Example ####
The data sent by the client:
```c
AT+IPMAC?
```
The response data obtained by the server:
```c
IP=192.168.1.10\r\n
MAC=12:34:56:78:9a:bc\r\n
OK\r\n
```
The pseudo code is parsed as follows:
```c
/* Create a server response structure, the maximum length of user-defined receive data is 128 */
resp = at_create_resp(128, 0, rt_tick_from_millisecond(5000));
at_exec_cmd(resp, "AT+IPMAC?");
/* Customize the parsing expression to parse the information in the current line number data */
at_resp_parse_line_args(resp, 1,"IP=%s", ip);
at_resp_parse_line_args(resp, 2,"MAC=%s", mac);
printf("IP=%s, MAC=%s\n", ip, mac);
at_delete_resp(resp);
```
The key to parsing data is to correctly define the expression. Because the response data of the different device manufacturers is not unique to the response data of the AT device, only the form of the custom parsing expression can be obtained to obtain the required information. The design of the `at_resp_parse_line_args` parsing parameter function is based on the `sscanf` data parsing method. Before using, the developer needs to understand the basic parsing syntax and then design the appropriate parsing syntax in combination with the response data. If the developer does not need to parse the specific parameters, you can use the `at_resp_get_line` function to get the specific data of a row.
### AT Client URC Data Processing ###
The processing of URC data is another important feature of AT Client. URC data is the data that is actively sent by the server. It cannot be received by the above data sending and receiving functions. The URC data format and function are different for different devices. Therefore, the URC data processing mode needs to be customized. The AT component provides a list management method for the processing of URC data. Users can customize the addition of URC data and its execution functions to the management list, so the processing of URC data is also the main porting work of AT Client.
Related structure:
```c
struct at_urc
{
const char *cmd_prefix; // URC data prefix
const char *cmd_suffix; // URC data suffix
void (*func)(const char *data, rt_size_t size); // URC data execution function
};
typedef struct at_urc *at_urc_t;
```
Each URC data has a structure control block that defines and determines the prefix and suffix of the URC data, as well as the execution function of the URC data. A piece of data can be defined as URC data only if it matches the prefix and suffix of the URC exactly. The URC data execution function is executed immediately after the matching URC data is obtained. So developers adding a URC data requires a custom matching prefix, suffix, and execution function.
#### URC Data List Initialization
```c
void at_set_urc_table(const struct at_urc *table, rt_size_t size);
```
| **Parameter** | **Description** |
|-----|-----------------------|
| table | URC data structure array pointer |
| size | Number of URC data |
This function is used to initialize the developer-defined URC data list, mainly used in the AT Client porting function.
The example of AT Client migration is given below. This example mainly shows the specific processing of URC data in the `at_client_port_init()` porting function. The developer can directly apply it to his own porting file, or customize the implementation function to complete the AT Client porting.
```c
static void urc_conn_func(const char *data, rt_size_t size)
{
/* WIFI connection success information */
LOG_D("AT Server device WIFI connect success!");
}
static void urc_recv_func(const char *data, rt_size_t size)
{
/* Received data from the server */
LOG_D("AT Client receive AT Server data!");
}
static void urc_func(const char *data, rt_size_t size)
{
/* Device startup information */
LOG_D("AT Server device startup!");
}
static struct at_urc urc_table[] = {
{"WIFI CONNECTED", "\r\n", urc_conn_func},
{"+RECV", ":", urc_recv_func},
{"RDY", "\r\n", urc_func},
};
int at_client_port_init(void)
{
/* Add multiple URC data to the URC list and execute the URC function when receiving data that matches both the URC prefix and the suffix */
at_set_urc_table(urc_table, sizeof(urc_table) / sizeof(urc_table[0]));
return RT_EOK;
}
```
### AT Client Other APIs Introduction
#### Send Specified Length Data
```c
rt_size_t at_client_send(const char *buf, rt_size_t size);
```
| **Parameter** | **Description** |
|----|-----------------------------|
| buf | Pointer to send data |
| size | Length of data sent |
| **Return** | -- |
| >0 | Successful, return the length of the data sent |
| <=0 | Failed |
This function is used to send the specified length data to the AT Server device through the AT Client device, which is mostly used for the AT Socket function.
#### Receive Specified Length Data
```c
rt_size_t at_client_recv(char *buf, rt_size_t size,rt_int32_t timeout);
```
| **Parameter** | **Description** |
|----|-----------------------------|
| buf | Pointer to receive data |
| size | Maximum length for receiving data |
| timeout | Receive data timeout (tick) |
| **Return** | -- |
| >0 | Successful, return the length of the data received successfully |
| <=0 | Failed, receiving data error or timeout |
This function is used to receive data of a specified length through the AT Client device, and is mostly used for the AT Socket function. This function can only be used in URC callback handlers.
#### Set the line terminator for receiving data ####
```c
void at_set_end_sign(char ch);
```
| Parameter | 描述 |
| ----- | ----- |
|ch | Line terminator |
| **Return** | **描述** |
|- | - |
This function is used to set the line terminator, which is used to judge the end of a row of data received by the client, and is mostly used for the AT Socket function.
#### Waiting for module initialization to complete ####
```c
int at_client_wait_connect(rt_uint32_t timeout);
```
| Parameter | Description |
| ----- | ----- |
|timeout | Waiting timeout |
| **Return** | **Description** |
|0 | Successful |
|<0 | Failed, no data returned during the timeout period |
This function is used to cyclically send AT commands when the AT module starts, until the module responds to the data, indicating that the module is successfully started.
### AT Client Multi-Client Support ###
In general, the device as the AT Client only connects to one AT module (the AT module acts as the AT Server) and can directly use the above functions of data transmission and reception and command parsing. In a few cases, the device needs to connect multiple AT modules as the AT Client. In this case, the multi-client support function of the device is required.
The AT component provides support for multi-client connections and provides two different sets of function interfaces: **Single-Client Mode Functions** and **Multi-Client Mode Functions**.
- Single-Client Mode Function: This type of function interface is mainly used when the device is connected to only one AT module, or when the device is connected to multiple AT modules, it is used in the **first AT client**.
- Multi-Client Mode Function: This type of function interface mainly uses devices to connect multiple AT modules.
The advantages and disadvantages of the two different mode functions and in different application scenarios are as follows:
![at client modes comparison](figures/at_multiple_client.jpg)
The single client mode function definition is mainly different from the single connection mode function. The definition of the incoming client object is different. The single client mode function uses the first initialized AT client object by default, and the multi-client mode function can Pass in the user-defined custom client object. The function to get the client object is as follows:
```c
at_client_t at_client_get(const char *dev_name);
```
This function obtains the AT client object created by the device through the incoming device name, which is used to distinguish different clients when connecting multiple clients.
The single client mode and multi-client mode function interface definitions differ from the following functions:
| Single-Client Mode Functions | Multi-Client Mode Functions |
| ----------------------------| ---------------------------------------|
| at_exec_cmd(...) | at_obj_exec_cmd(client, ...) |
| at_set_end_sign(...) | at_obj_set_end_sign(client, ...) |
| at_set_urc_table(...) | at_obj_set_urc_table(client, ...) |
| at_client_wait_connect(...) | at_client_obj_wait_connect(client, ...) |
| at_client_send(...) | at_client_obj_send(client, ...) |
| at_client_recv(...) | at_client_obj_recv(client, ...) |
The two modes of client data transmission and parsing are basically the same, and the function usage process is different, as shown below:
```c
/* Single client mode function usage */
at_response_t resp = RT_NULL;
at_client_init("uart2", 512);
resp = at_create_resp(256, 0, 5000);
/* Send commands using a single client mode function */
at_exec_cmd(resp, "AT+CIFSR");
at_delete_resp(resp);
```
```c
/* Multi-client mode functions usage */
at_response_t resp = RT_NULL;
at_client_t client = RT_NULL;
/* Initialize two AT clients */
at_client_init("uart2", 512);
at_client_init("uart3", 512);
/* Get the corresponding AT client object by name */
client = at_client_get("uart3");
resp = at_create_resp(256, 0, 5000);
/* Send commands using multi-client mode functions */
at_obj_exec_cmd(client, resp, "AT+CIFSR");
at_delete_resp(resp);
```
The process differences used by other functions are similar to the above `at_obj_exec_cmd()` function. The main function is to obtain the client object through the `at_client_get() ` function, and then determine which client is the client through the incoming object to achieve multi-client support.
## FAQs
### Q: What should I do if the log on the shell shows an error when enabling the AT command to send and receive data real-time printing function. ?
**A:** Increase the baudrate of the serial port device corresponding to the shell to 921600, improve the serial port printing speed, and prevent the printing error when the data is too large.
### Q: When the AT Socket function is started, the compile prompt "The AT socket device is not selected, please select it through the env menuconfig".
**A:** After the AT Socket function is enabled, the corresponding device model is enabled in the at device package by default. Enter the at device package, configure the device as an ESP8266 device, configure WIFI information, re-generate the project, compile and download.
### Q: AT Socket function data reception timeout or data reception is not complete.
**A:** The error may be that the receive data buffer in the serial device used by the AT is too small (RT_SERIAL_RB_BUFSZ default is 64 bytes), and the data is overwritten after the data is not received in time. The buffer size of the serial port receiving data (such as 256 bytes) is appropriately increased.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,754 @@
# Kernel Basics
This chapter gives a brief introduction to the software architecture of the RT-Thread kernel, beginning with its composition and implementation. While also introducing RT-Thread kernel-related concepts for beginners.
After understanding this chapter, readers will have an elementary understanding of the RT Thread kernel and will be able to answer questions such as -
- What are the constituents of the kernel?
- How does the system startup?
- How is the memory distributed?
- What are the methods of kernel configuration?
In the nutshell, this is only a brief introduction to software architecture decomposition and implementation of the real-time kernel. This will give understanding and concepts of how RT-Thread kernel works togther. After learning from this chapter, readers will have basic knowledge of each kernel components, system booting up proccesses, memory allocation and distrubtion, and methods of kernel configuration.
## **Table of Contents**
1. [Introduction to RT-Thread Kernel](#introduction-to-rt-thread-kernel)
2. [RT-Thread Startup Process](#rt-thread-startup-process)
3. [RT-Thread Program Memory Distribution](#rt-thread-program-memory-distribution)
4. [RT-Thread Automatic Initialization Mechanism](#rt-thread-automatic-initialization-mechanism)
5. [RT-Thread Kernel Object Model](#rt-thread-kernel-object-model)
## Introduction to RT-Thread Kernel
Kernel is the most basic and fundenmental part of an Operating System. Kernel service library and RT-Thread kernel libraries are interfacing between hardware and components/service layer. This includes the implementation of real-time kernel service library (rtservice.h/kservice.c) and other RT-Thread kernel libraries such as object management, thread management and scheduler, inter-thread communication management, clock management and memory management respectively. Below diagram is the core architecture diagram of the core kernel.
![RT-Thread Kernel and its Substructure](figures/03kernel_Framework.png)
Implementation of core kernel libraries are similar to a small set of standard C runtime library and it can run independently. For example, Standard C library (C runtime library) provides "strcpy", "memcpy", "printf", "scanf" and other function implementations. RT-Thread kernel libraries also provide the function implementations which are mainly used by Core Kernel. However, to avoid name conflicts, specifically functions' names are preceded with rt_.
The built of the Kernel will be vary depending on the complier. For example, using GNU GCC compiler, it will use more implementation from the standard C library. Last but not least, the minimum resource requirements of the Kernel is 3KB ROM and 1.2KB RAM.
### Thread Scheduling
Thread is the smallest scheduling unit in the RT-Thread operating system. The thread scheduling algorithm is a **Priority-based Full Preemptive Multi-Thread** scheduling algorithm.
The system can support up to 256(0 - 255) thread priorities. For systems with tight resources, configurations with 8 or 32 thread priorities can be chosen(For example, STM32 has 32 thread priorities as per the default configuration). Lower numbers have a higher priority where 0 represents the highest priority furthermore the lowest priority(highest number) is reserved for idle threads.
RT-Thread supports the creation of multiple threads with the same priority. Threads having the same priority are scheduled with a Time Slice Rotation Scheduling algorithm so that each thread runs for the same amount of time.
The number of threads is bounded by the memory of the hardware platform and not the system.
Thread management will be covered in detail in the "Thread Management" chapter.
### Clock Management
RT-Thread's Clock management is based upon a **clock beat**, which is the smallest clock unit in the RT-Thread operating system.
The RT-Thread timer provides two types of timer mechanisms:
- **One-Shot Timer** - Triggers only one timer event after startup and then stops automatically.
- **Periodic Trigger Timer** - Periodically triggers timer events until the user manually stops the timer or it will continue to operate.
The RT-Thread timer can be set to the `HARD_TIMER` or the `SOFT_TIMER` mode depending on the context in which the timeout function is executed.
The timer service is concluded using a timer timing callback i.e. a timeout function. The user can select the appropriate type of timer according to their real-time requirements for timing processing.
Timer will be explained further in the "Clock Management" chapter.
### Synchronization between Threads
RT-Thread uses thread semaphores, mutexes, and event sets to achieve inter-thread synchronization.
Thread synchronizations happen through the acquisition and release of semaphore and mutexes.
The mutex uses priority inheritance to solve the common priority inversion problem in the real-time system. The thread synchronization mechanism allows threads to wait according to priorities or to acquire semaphores/mutexes following the First In First Out(FIFO) method.
Event sets are primarily used for synchronization between threads, they can achieve one-to-many and many-to-many synchronization. It allows "**OR** trigger"(*independent synchronization*) and "**AND** trigger"(*associative synchronization*) suitable for situations where threads are waiting for multiple events.
The concepts of semaphores, mutexes, and event sets are detailed in the "Inter-Thread Synchronization" chapter.
### Inter-Thread Communication
RT-Thread supports communication mechanisms such as mailbox, message queue, etc. The mailbox's message length is fixed to 4 bytes. Whereas, message queue can receive messages in variable size and cache the messages in its own memory space.
Compared to a message queue, a mailbox is more efficient. The sending action of the mailbox and message queue can be safely used in an ISR (Interrupt Service Routine). The communication mechanism allows threads to wait by priority or to acquire by the First In First Out (FIFO) method.
The concept of mailbox and message queue will be explained in detail in the "Inter-Thread Communication" chapter.
### Memory Management
RT-Thread allows:
1. Static Memory Pool
2. Dynamic Memory Pool
When the static memory pool has available memory, the time allocated to the memory block will be constant.
When the static memory pool is empty, the system will then request for suspending or blocking the thread of the memory block. The thread will abandon the request and return if the memory block is not obtained after waiting for a while, or the thread will abandon and return immediately. The waiting time depends on the waiting time parameter set when the memory block is applied. When other threads release the memory block to the memory pool, the system will wake up the thread if there are suspended threads waiting to be allocated of memory blocks.
Under circumstances of different system resources, the dynamic memory heap management module respectively provides memory management algorithms for small memory systems and SLAB memory management algorithms for large memory systems.
There is also a dynamic memory heap management called memheap, suitable for memory heaps in systems with multiple addresses that can be discontinuous. Using memheap, the user can "paste" multiple memory heaps together, letting them operate as if operating a memory heap.
The concept of memory management will be explained in the "Memory Management" chapter.
### I/O Device Management
RT-Thread uses I2C, SPI, USB, UART, etc., as peripheral devices and is uniformly registered through the device. It realized a device management subsystem accessed by the name, and it can access hardware devices according to a unified API interface. On the device driver interface, depending on the characteristics of the embedded system, corresponding events can be attached to different devices. The driver notifies the upper application program when the device event is triggered.
The concept of I/O device management will be explained in the "Device Model" and "General Equipment" chapters.
## RT-Thread Startup Process
The understanding of most codes usually starts from learning the startup process. We will firstly look for the source of the startup. Taking MDK-ARM as an example, the user program entry for MDK-ARM is the main() function located in the main.c file. The launching of the system starts from the assembly code startup_stm32f103xe.s, jumps to the C code, initializes the RT-Thread system function, and finally enters the user program entry main().
To complete the RT-Thread system function initialization before entering main(), we used the MDK extensions `$Sub$$` and `$Super$$`. Users can add the prefix of `$Sub$$` to main to make it a new function `$Sub$$main`.
`$Sub$$main` can call some functions to be added before main (here, RT-Thread system initialization function is added). Then, call `$Super$$main` to the main() function so that the user does not have to manage the system initialization before main().
For more information on the use of the `$Sub$$` and `$Super$$`extensions, see the ARM® Compiler v5.06 for μVision®armlink User Guide.
Let's take a look at this code defined in components.c:
```c
/* $Sub$$main Function */
int $Sub$$main(void)
{
rtthread_startup();
return 0;
}
```
Here, the `$Sub$$main` function simply calls the rtthread_startup() function. RT-Thread allows multiple platforms and multiple compilers, and the rtthread_startup() function is a uniform entry point specified by RT-Thread, so the `$Sub$$main` function only needs to call the rtthread_startup() function (RT-Thread compiled using compiler GNU GCC is an example where it jumps directly from the assembly startup code section to the rtthread_startup() function and starts the execution of the first C code).
The rtthread_startup() function can be found in the code of components.c, the startup process of RT-Thread is as shown below:
![System startup process](figures/03Startup_process.png)
Code for the rtthread_startup() function is as follows:
```c
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* Board level initialization: system heap initialization is required inside the function */
rt_hw_board_init();
/* Print RT-Thread version information */
rt_show_version();
/* Timer initialization */
rt_system_timer_init();
/* Scheduler initialization */
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/* Signal initialization */
rt_system_signal_init();
#endif
/* Create a user main() thread here */
rt_application_init();
/* Timer thread initialization */
rt_system_timer_thread_init();
/* Idle thread initialization */
rt_thread_idle_init();
/* Start scheduler */
rt_system_scheduler_start();
/* Will not execute till here */
return 0;
}
```
This part of the startup code can be roughly divided into four parts:
1. Initialize hardware related to the system.
2. Initialize system kernel objects, such as timers, schedulers, and signals.
3. Create the main thread initialize various modules in the main thread one by one.
4. Initialize the timer thread, idle thread, and start the scheduler.
Set the system clock in rt_hw_board_init() to provide heartbeat and serial port initialization for the system, bound to the system's input and output terminals to this serial port. Subsequent system operation information will be printed out from the serial port later.
The main() function is the user code entry for RT-Thread, and users can add their own applications to the main() function.
```c
int main(void)
{
/* user app entry */
return 0;
}
```
## RT-Thread Program Memory Distribution
The general MCU contains storage space that includes: on-chip Flash and on-chip RAM, RAM is equivalent to memory, and Flash is equivalent to hard disk. The compiler classifies a program into several parts, which are stored in different memory areas of the MCU.
After the Keil project is compiled, there will prompt information for occupied space by the corresponding program, as shown below:
```
linking...
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build - User command \#1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin
".\\build\\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07
```
The Program Size mentioned above contains the following sections:
1) Code: code segment, section of code that store the program;
2) RO-data: read-only data segment, stores the constants defined in the program;
3) RW-data: read and write data segment, stores global variables initialized to non-zero values;
4) ZI-data: 0 data segment, stores uninitialized global variables and initialized-to-0 variables;
After compiling the project, there will be a generated .map file that describes the size and address of each function. The last few lines of the file also illustrate the relationship between the above fields:
```
Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
```
1) RO Size contains Code and RO-data, indicating the size of the Flash occupied by the program;
2) RW Size contains RW-data and ZI-data, indicating the size of the RAM occupied when operating;
3) ROM Size contains Code, RO Data and RW Data, indicating the size of the Flash occupied by the programming system;
Before the program runs, the file entity need to be burned into STM32 Flash, usually it is bin or hex file. The burned file is called executable image file. The left figure, Figure 3-3, shows the memory distribution after the executable image file is burned to STM32 which includes two parts: the RO segment and the RW segment. The RO segment stores data of Code and RO-data and the RW segment holds the data of RW-data. Since ZI-data is 0, it is not included in the image file.
STM32 is launched from Flash by default after power-on. After launching, RW-data (initialized global variable) in RW segment is transferred to RAM, but RO segment is not transferred. This means execution code of CPU is read from Flash, the ZI segment is allocated according to the ZI address and size given by the compiler, and the RAM area is cleared.
![RT-Thread Memory Distribution](figures/03Memory_distribution.png)
The dynamic memory heap is unused RAM space, and the memory blocks requested and released by the application come from this space.
As the following example:
```c
rt_uint8_t* msg_ptr;
msg_ptr = (rt_uint8_t*) rt_malloc (128);
rt_memset(msg_ptr, 0, 128);
```
The 128-byte memory space pointed to by the msg_ptr pointer in the code is in the dynamic memory heap space.
Some global variables are stored in the RW segment and the ZI segment. The RW segment stores the global variable with the initial value (the global variable in the constant form is placed in the RO segment, which is a read-only property), and uninitialized global variable is stored in the ZI segment, as in the following example:
```c
#include <rtthread.h>
const static rt_uint32_t sensor_enable = 0x000000FE;
rt_uint32_t sensor_value;
rt_bool_t sensor_inited = RT_FALSE;
void sensor_init()
{
/* ... */
}
```
The sensor_value is stored in the ZI segment and is automatically initialized to zero after system startup (some library functions provided by the user program or compiler are initialized to zero). The sensor_inited variable is stored in the RW segment, and the sensor_enable is stored in the RO segment.
RT-Thread Automatic Initialization Mechanism
-----------------------
The automatic initialization mechanism means that the initialization function does not need to be called by explicit function. It only needs to be declared by macro definition at the function definition, and it will be executed during system startup.
For example, calling a macro definition in the serial port driver to inform the function that needs to be called to initialize the system. The code is as follows:
```c
int rt_hw_usart_init(void) /* Serial port initialization function */
{
... ...
/* Register serial port 1 device */
rt_hw_serial_register(&serial1, "uart1",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
uart);
return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); /* Use component auto-initialization mechanism */
```
The last part of the sample code INIT_BOARD_EXPORT(rt_hw_usart_init) indicates that the automatic initialization function is used. In this way, rt_hw_usart_init() function is automatically called by the system, so where is it called?
In the system startup flowchart, there are two functions: rt_components_board_init() and rt_components_init(), subsequent functions inside the box with the underlying color represent functions that are automatically initialized, where:
1. “board init functions” are all initialization functions declared by INIT_BOARD_EXPORT(fn).
2. “pre-initialization functions” are all initialization functions declared by INIT_PREV_EXPORT(fn).
3. “device init functions” are all initialization functions declared by INIT_DEVICE_EXPORT(fn).
4. “components init functions” are all initialization functions declared by INIT_COMPONENT_EXPORT(fn).
5. “enviroment init functions” are all initialization functions declared by INIT_ENV_EXPORT(fn).
6. “application init functions” are all initialization functions declared by INIT_APP_EXPORT(fn).
The rt_components_board_init() function executes earlier, mainly to initialize the relevant hardware environment. When this function is executed, it will traverse the initialization function table declared by INIT_BOARD_EXPORT(fn) and call each function.
The rt_components_init() function is called and executed in the main thread created after the operating system is running. At this time, the hardware environment and the operating system have been initialized and the application-related code can be executed. The rt_components_init() function will transverse through the remaining few initialization function tables declared by macros.
RT-Thread's automatic initialization mechanism uses a custom RTI symbol segment, it puts the function pointer that needs to be initialized at startup into this segment and forms an initialization function table which will be traversed during system startup. It calls the functions in the table to achieve the purpose of automatic initialization.
The macro interface definitions used to implement the automatic initialization function are described in the following table:
|Initialization sequence|Macro Interface |Description |
|----------------|------------------------------------|----------------------------------------------|
| 1 | INIT_BOARD_EXPORT(fn) | Very early initialization, the scheduler has not started yet. |
| 2 | INIT_PREV_EXPORT(fn) | Mainly used for pure software initialization, functions without too many dependencies |
| 3 | INIT_DEVICE_EXPORT(fn) | Peripheral driver initialization related, such as network card devices |
| 4 | INIT_COMPONENT_EXPORT(fn) | Component initialization, such as file system or LWIP |
| 5 | INIT_ENV_EXPORT(fn) | System environment initialization, such as mounting file systems |
| 6 | INIT_APP_EXPORT(fn) | Application initialization, such as application GUI |
Initialization function actively declares through these macro interfaces, such as INIT_BOARD_EXPORT (rt_hw_usart_init), the linker will automatically collect all the declared initialization functions, placed in the RTI symbol segment, the symbol segment is located in the RO segment of the memory distribution. All functions in this RTI symbol segment are automatically called when the system is initialized.
RT-Thread Kernel Object Model
---------------------
### Static and Dynamic Objects
The RT-Thread kernel is designed with object-oriented method. The system-level infrastructures are all kernel objects such as threads, semaphores, mutexes, timers, and more. Kernel objects fall into two categories: static kernel objects and dynamic kernel objects. Static kernel objects are usually placed in RW and ZI segments, initialized in the program after system startup; dynamic kernel objects are created from the memory heap and then manually initialized.
The following code is an example of static threads and dynamic threads:
```c
/* Thread 1 object and stack used while running */
static struct rt_thread thread1;
static rt_uint8_t thread1_stack[512];
/* Thread 1 entry */
void thread1_entry(void* parameter)
{
int i;
while (1)
{
for (i = 0; i < 10; i ++)
{
rt_kprintf("%d\n", i);
/* Delay 100ms */
rt_thread_mdelay(100);
}
}
}
/* Thread 2 entry */
void thread2_entry(void* parameter)
{
int count = 0;
while (1)
{
rt_kprintf("Thread2 count:%d\n", ++count);
/* Delay 50ms */
rt_thread_mdelay(50);
}
}
/* Thread routine initialization */
int thread_sample_init()
{
rt_thread_t thread2_ptr;
rt_err_t result;
/* Initialize thread 1 */
/* The thread entry is thread1_entry and the parameter is RT_NULL
* Thread stack is thread1_stack
* Priority is 200 and time slice is 10 OS Tick
*/
result = rt_thread_init(&thread1,
"thread1",
thread1_entry, RT_NULL,
&thread1_stack[0], sizeof(thread1_stack),
200, 10);
/* Start thread */
if (result == RT_EOK) rt_thread_startup(&thread1);
/* Create thread 2 */
/* The thread entry is thread2_entry and the parameter is RT_NULL
* Stack space is 512, priority is 250, and time slice is 25 OS Tick
*/
thread2_ptr = rt_thread_create("thread2",
thread2_entry, RT_NULL,
512, 250, 25);
/* Start thread */
if (thread2_ptr != RT_NULL) rt_thread_startup(thread2_ptr);
return 0;
}
```
In this example, thread1 is a static thread object and thread2 is a dynamic thread object. The memory space of the thread1 object, including the thread control block thread1 and the stack space thread1_stack are all determined while compiling, because there is no initial value in the code and they are uniformly placed in the uninitialized data segment. The space used by thread2 is dynamically allocated includes the thread control block (the content pointed to by thread2_ptr) and the stack space.
Static objects take up RAM space and is not depend on the memory heap manager. When allocating static objects, the time needed is determined. Dynamic objects depend on the memory heap manager. It requests RAM space while running. When the object is deleted, the occupied RAM space is released. These two methods have their own advantages and disadvantages, and can be selected according to actual needs.
### Kernel Object Management Structure
RT-Thread uses the kernel object management system to access/manage all kernel objects. Kernel objects contain most of the facilities in the kernel. These kernel objects can be statically allocated static objects and dynamic objects allocated from the system memory heap. .
Because of this design for kernel object, RT-Thread is able to not depend on the specific memory allocation method, and the flexibility of the system is greatly improved.
RT-Thread kernel objects include: threads, semaphores, mutexes, events, mailboxes, message queues and timers, memory pools, device drivers, and more. The object container contains information about each type of kernel object, including object type, size, and so on. The object container assigns a linked list to each type of kernel object. All kernel objects are linked to the linked list. The kernel object container and linked list of RT-Thread are shown in the following figure:
![RT-Thread Kernel Object Container and Linked List](figures/03kernel_object.png)
The following figure shows the derivation and inheritance relationships of various kernel objects in RT-Thread. For each specific kernel object and object control block, in addition to the basic structure, they have their own extended attributes (private attributes). Take thread control block for an example, the base object is extended, attributes like thread state, precedence and so on are added. These attributes are not used in the operation of the base class object and are only used in operations related to a specific thread. Therefore, from the object-oriented point of view, each concrete object can be considered as a derivative of an abstract object, inheriting the attributes of the base object and extending the attributes related to itself.
![RT-Thread Kernel Object Inheritance Relationship](figures/03kernel_object2.png)
In the object management module, a common data structure is defined to store the common attributes of various objects. Each specific object only needs to add some special attributes of its own and its own feature will be clearly expressed.
The advantages of this design approach are:
(1) Improve the reusability and scalability of the system. It is easy to add new object categories. It only needs to inherit the attributes of the general object and add a small amount of extension.
(2) Provide a unified object operation mode, simplify the operation of various specific objects, and improve the reliability of the system.
Derivations from object control block rt_object in the above figure includes: thread object, memory pool object, timer object, device object and IPC object (IPC: Inter-Process Communication. In RT-Thread real-time operating system, IPC objects is used for synchronization and communicate between threads); derivations from IPC objects includes: semaphores, mutexes, events, mailboxes, message queues, signals, etc.
### Object Control Block
Data structure of kernel object control block:
```c
struct rt_object
{
/* Kernel object name */
char name[RT_NAME_MAX];
/* Kernel object type */
rt_uint8_t type;
/* Parameters to the kernel object */
rt_uint8_t flag;
/* Kernel object management linked list */
rt_list_t list;
};
```
Types currently supported by kernel objects are as follows:
```c
enum rt_object_class_type
{
RT_Object_Class_Thread = 0, /* Object is thread type */
#ifdef RT_USING_SEMAPHORE
RT_Object_Class_Semaphore, /* Object is semaphore type */
#endif
#ifdef RT_USING_MUTEX
RT_Object_Class_Mutex, /* Object is mutex type */
#endif
#ifdef RT_USING_EVENT
RT_Object_Class_Event, /* Object is event type */
#endif
#ifdef RT_USING_MAILBOX
RT_Object_Class_MailBox, /* Object is mailbox type */
#endif
#ifdef RT_USING_MESSAGEQUEUE
RT_Object_Class_MessageQueue, /* Object is message queue type */
#endif
#ifdef RT_USING_MEMPOOL
RT_Object_Class_MemPool, /* Object is memory pool type */
#endif
#ifdef RT_USING_DEVICE
RT_Object_Class_Device, /* Object is device type */
#endif
RT_Object_Class_Timer, /* Object is timer type */
#ifdef RT_USING_MODULE
RT_Object_Class_Module, /* Object is module */
#endif
RT_Object_Class_Unknown, /* Object is unknown */
RT_Object_Class_Static = 0x80 /* Object is a static object */
};
```
From the above type specification, we can see that if it is a static object, the highest bit of the object type will be 1 (which is the OR operation of RT_Object_Class_Static and other object types and operations). Otherwise it will be dynamic object, and the maximum number of object classes that the system can accommodate is 127.
### Kernel Object Management
Data structure of kernel object container:
```c
struct rt_object_information
{
/* Object type */
enum rt_object_class_type type;
/* Object linked list */
rt_list_t object_list;
/* Object size */
rt_size_t object_size;
};
```
A class of objects is managed by an rt_object_information structure, and each practical instance of such type of object is mounted to the object_list in the form of a linked list. The memory block size of this type of object is identified by object_size (the memory block each practical instance of each type of object is the same size).
#### Initialization Object
An uninitialized static object must be initialized before it can be used. The initialization object uses the following interfaces:
```c
void rt_object_init(struct rt_object* object ,
enum rt_object_class_type type ,
const char* name)
```
When this function is called to initialize the object, the system will place the object into the object container for management, that is, initialize some parameters of the object, and then insert the object node into the object linked list of the object container. Input parameters of the function is described in the following table:
|Parameters|Description |
| -------- | ------------------------------------------------------------ |
| object | The object pointer that needs to be initialized must point to a specific object memory block, not a null pointer or a wild pointer. |
| type | The type of the object must be a enumeration type listed in rt_object_class_type, RT_Object_Class_Static excluded. (For static objects, or objects initialized with the rt_object_init interface, the system identifies it as an RT_Object_Class_Static type) |
| name | Name of the object. Each object can be set to a name, and the maximum length for the name is specified by RT_NAME_MAX. The system does not care if it uses `\0`as a terminal symbol. |
#### Detach Object
Detach an object from the kernel object manager. The following interfaces are used to detach objects:
```c
void rt_object_detach(rt_object_t object);
```
Calling this interface makes a static kernel object to be detached from the kernel object container, meaning the corresponding object node is deleted from the kernel object container linked list. After the object is detached, the memory occupied by the object will not be released.
#### Allocate object
The above descriptions are interfaces of objects initialization and detachment, both of which are under circumstances that object-oriented memory blocks already exist. But dynamic objects can be requested when needed. The memory space is freed for other applications when not needed. To request assigning new objects, you can use the following interfaces:
```c
rt_object_t rt_object_allocate(enum rt_object_class_typetype ,
const char* name)
```
When calling the above interface, the system first needs to obtain object information according to the object type (especially the size information of the object type for the system to allocate the correct size of the memory data block), and then allocate memory space corresponding to the size of the object from the memory heap. Next, to start necessary initialization for the object, and finally insert it into the object container linked list in which it is located. The input parameters for this function are described in the following table:
|Parameters |Description |
| ------------------ | ------------------------------------------------------------ |
| type | The type of the allocated object can only be of type rt_object_class_type other than RT_Object_Class_Static. In addition, the type of object allocated through this interface is dynamic, not static. |
| name | Name of the object. Each object can be set to a name, and the maximum length for the name is specified by RT_NAME_MAX. The system does not care if it uses `\0`as a terminal symbol. |
|**Return** | —— |
| object handle allocated successfully | Allocate successfully |
| RT_NULL | Fail to allocate |
#### Delete Object
For a dynamic object, when it is no longer used, you can call the following interface to delete the object and release the corresponding system resources:
```c
void rt_object_delete(rt_object_t object);
```
When the above interface is called, the object is first detached from the object container linked list, and then the memory occupied by the object is released. The following table describes the input parameters of the function:
|Parameter|Description |
|----------|------------|
| object | object handle |
#### Identify objects
Identify whether the specified object is a system object (static kernel object). The following interface is used to identify the object:
```c
rt_err_t rt_object_is_systemobject(rt_object_t object);
```
Calling the rt_object_is_systemobject interface can help to identify whether an object is a system object. In RT-Thread operating system, a system object is also a static object, RT_Object_Class_Static bit is set to 1 on the object type identifier. Usually, objects that are initialized using the rt_object_init() method are system objects. The input parameters for this function are described in the following table:
Input parameter of rt_object_is_systemobject()
|**Parameter**|Description |
|----------|------------|
| object | Object handle |
RT-Thread Kernel Configuration Example
----------------------
An important feature of RT-Thread is its high degree of tailorability, which allows for fine-tuning of the kernel and flexible removal of components.
Configuration is mainly done by modifying the file under project directory - rtconfig.h. User can conditionally compile the code by opening/closing the macro definition in the file, and finally achieve the purpos e of system configuration and cropping, as follows:
1RT-Thread Kernel part
```c
/* Indicates the maximum length of the name of the kernel object. If the maximum length of the name of the object in the code is greater than the length of the macro definition,
* the extra part will be cut off. */
#define RT_NAME_MAX 8
/* Set the number of aligned bytes when bytes are aligned. Usually use ALIGN(RT_ALIGN_SIZE) for byte alignment.*/
#define RT_ALIGN_SIZE 4
/* Define the number of system thread priorities; usually define the priority of idle threads with RT_THREAD_PRIORITY_MAX-1 */
#define RT_THREAD_PRIORITY_MAX 32
/* Define the clock beat. When it is 100, it means 100 tick per second, and a tick is 10ms. */
#define RT_TICK_PER_SECOND 100
/* Check if the stack overflows, if not defined, close. */
#define RT_USING_OVERFLOW_CHECK
/* Define this macro to enable debug mode, if not defined, close. */
#define RT_DEBUG
/* When debug mode is enabled: When the macro is defined as 0, the print component initialization information is turned off. When it is defined as 1, it is enabled. */
#define RT_DEBUG_INIT 0
/* When debug mode is enabled: When the macro is defined as 0, the print thread switching information is turned off. When it is defined as 1, it is enabled. */
#define RT_DEBUG_THREAD 0
/* Defining this macro means the use of the hook function is started, if not defined, close. */
#define RT_USING_HOOK
/* Defines the stack size of idle threads. */
#define IDLE_THREAD_STACK_SIZE 256
```
2Inter-thread synchronization and communication part, the objects that will be used in this part are semaphores, mutexes, events, mailboxes, message queues, signals, and so on.
```c
/* Define this macro to enable the use of semaphores, if not defined, close. */
#define RT_USING_SEMAPHORE
/* Define this macro to enable the use of mutexes, if not defined, close. */
#define RT_USING_MUTEX
/* Define this macro to enable the use of events, if not defined, close. */
#define RT_USING_EVENT
/* Define this macro to enable the use of mailboxes, if not defined, close. */
#define RT_USING_MAILBOX
/* Define this macro to enable the use of message queues, if not defined, close. */
#define RT_USING_MESSAGEQUEUE
/* Define this macro to enable the use of signals, if not defined, close. */
#define RT_USING_SIGNALS
```
3Memory Management Part
```c
/* Start the use of static memory pool */
#define RT_USING_MEMPOOL
/* Define this macro to start the concatenation of two or more memory heap , if not defined, close. */
#define RT_USING_MEMHEAP
/* Start algorithm for small memory management */
#define RT_USING_SMALL_MEM
/* Turn off SLAB memory management algorithm */
/* #define RT_USING_SLAB */
/* Start the use of heap */
#define RT_USING_HEAP
```
4Kernel Device Object
```c
/* Indicates the start of useing system devices */
#define RT_USING_DEVICE
/* Define this macro to start the use of system console devices, if not defined, close. */
#define RT_USING_CONSOLE
/* Define the buffer size of the console device. */
#define RT_CONSOLEBUF_SIZE 128
/* Name of the console device. */
#define RT_CONSOLE_DEVICE_NAME "uart1"
```
5Automatic Initialization Method
```c
/* Define this macro to enable automatic initialization mechanism, if not defined, close. */
#define RT_USING_COMPONENTS_INIT
/* Define this macro to set application entry as main function */
#define RT_USING_USER_MAIN
/* Define the stack size of the main thread */
#define RT_MAIN_THREAD_STACK_SIZE 2048
```
6FinSH
```c
/* Define this macro to start the use of the system FinSH debugging tool, if not defined, close. */
#define RT_USING_FINSH
/* While starting the system FinSH: the thread name is defined as tshell */
#define FINSH_THREAD_NAME "tshell"
/* While turning the system FinSH: use history commands. */
#define FINSH_USING_HISTORY
/* While turning the system FinSH: define the number of historical command lines. */
#define FINSH_HISTORY_LINES 5
/* While turning the system FinSH: define this macro to open the Tab key, if not defined, close. */
#define FINSH_USING_SYMTAB
/* While turning the system FinSH: define the priority of the thread. */
#define FINSH_THREAD_PRIORITY 20
/* While turning the system FinSHdefine the stack size of the thread. */
#define FINSH_THREAD_STACK_SIZE 4096
/* While turning the system FinSHdefine the length of command character. */
#define FINSH_CMD_SIZE 80
/* While turning the system FinSH: define this macro to enable the MSH function. */
#define FINSH_USING_MSH
/* While turning the system FinSHwhen MSH function is enabled, macro is defined to use the MSH function by default. */
#define FINSH_USING_MSH_DEFAULT
/* While turning the system FinSHdefine this macro to use only the MSH function. */
#define FINSH_USING_MSH_ONLY
```
7About MCU
```c
/* Define the MCU used in this project is STM32F103ZE; the system defines the chip pins by defining the chip type. */
#define STM32F103ZE
/* Define the clock source frequency. */
#define RT_HSE_VALUE 8000000
/* Define this macro to enable the use of UART1. */
#define RT_USING_UART1
```
>In practice, the system configuration file rtconfig.h is automatically generated by configuration tools and does not need to be changed manually.
Common Macro Definition Description
--------------
Macro definitions are often used in RT-Thread. For example, some common macro definitions in the Keil compilation environment:
1rt_inline, definition is as follows, static keyword is to make the function only available for use in the current file; inline means inline, after modification using static, the compiler is recommended to perform inline expansion when calling the function.
```c
#define rt_inline static __inline
```
2RT_USEDdefinition is as follows, the purpose of this macro is to explain to the compiler that this code is useful, compilation needs to be saved even if it is not called in the function. For example, RT-Thread auto-initialization uses custom segments, using RT_USED will retain custom code snippets.
```c
#define RT_USED __attribute__((used))
```
3RT_UNUSEDdefinition is as follows, indicates that a function or variable may not be used. This attribute prevents the compiler from generating warnings.
```c
#define RT_UNUSED __attribute__((unused))
```
4RT_WEAKdefinition is as follows, often used to define functions, when linking the function, the compiler will link the function without the keyword prefix first and link the function modified by weak if it can't find those functions.
```c
#define RT_WEAK __weak
```
5ALIGN(n)definition is as follows, is used to align its stored address with n bytes when allocating an address space to an object. Here, n can be the power of 2. Byte alignment not only facilitates quick CPU access, but also save memory space if byte alignment is properly used.
```c
#define ALIGN(n) __attribute__((aligned(n)))
```
6RT_ALIGN(size,align)definition is as follows, to increase size to a multiple of an integer defined by align. For example, RT_ALIGN(13,4) will return to 16.
```c
#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,171 @@
# Contribution Guide
We sincerely thank you for your contribution, and welcome to submit the code through GitHub's fork and Pull Request processes.
First, explain the word Pull Request. Pull request means to send a request. The purpose of the developer initiating Pull Request is to request the repository maintainer to adopt the code submitted by the developer.
When you want to correct mistakes in other people's repositories, follow the following procedure:
- To fork someone else's repository is equivalent to copying someone else's information. Because you can't guarantee that your modification is correct and beneficial to the project, you can't modify it directly in someone else's repository, but first fork it into your own git repository.
- Clone code to your own PC local, create a new branch, modify bugs or add new features, and then launch pull request to the original repository, so that the original repository manager can see the changes you submitted.
- The original repository manager reviews this submission and, if correct, merge it into his own project. Merge means merging, merging the part of code you modified into the original repository to add code or replace the original code. So far, the whole Pull Request process is over.
## Coding Style
Refer to the `coding_style_en.txt` file in the rt_thread project documentation directory for the RT-Thread code programming style.
## Preparation
Install Git: You need to add Git's directory to the system environment variable.
## Contribution Process
Now take RT-Thread repository as an example to illustrate the process of contributing code:
### Fork
Fork the RT-Thread/rt-thread repository into your git repository.
![fork rt-thread repository](figures/cloneformgit.png)
### Clone
In your repository, copy the repository links after your fork:
![clone rt-thread from your repo](figures/cloneformgit2.png)
You can use the `git clone` command to copy the repository to your PC:
```
git clone [url]
```
![git clone](figures/git_clone.png)
### Create a New Branch
It is recommended that you create your own development branch based on the master branch, and use following commands to create a new branch:
```
git checkout -b YourBranchName
```
For example, create a branch named "dev": `git checkout -b dev`.
### Developing
Modify bugs and submit new functional code. For example, suppose the developer adds a USB driver:
![Add a USB driver](figures/add_usb_driver.png)
### Temporarily Store Modified Files
Add all changes to the temporary area:
```
git add .
```
If you only want to add some specified files to the temporary area, use other commands of `git add`.
### Commit
Submit this modification to the local repository:
```
git commit -m "Describe your submission here"
```
> Note: If there are multiple commits in the local development branch, in order to ensure that the RT-Thread repository commit is clean, please tidy up the local commits. More than five commits are not accepted by Pull Request.。
### Push to Your Remote Repository
Push the modified content to the branch of your remote repository. It is recommended that the branch name of the remote repository be consistent with the local branch name.Use the following command to push:
```
git push origin YourBranchName
```
### Create a Pull Request
Enter the RT-Thread repository under your Github account and click `New pull request -> Create pull request`. Make sure you choose the right branch.
![Create a Pull Request](figures/pull_request_step2.png)
Step 1: Fill in the title of this Pull Request
Step 2: Modify the description information of this Pull Request (modify it in `Write` and preview it with `Preview`):
- Modify PR Description: Replace the content in the red box below with the description of this pull request according to the requirements in the red box below.
- Check PR Options: Fill in [x] in the OK Options check box to confirm. Note that there are no spaces on both sides of [x].
![Modify PR Description and Check PR Options](figures/pr_description.png)
Step 3Create pull request.
### Sign CLA
The first contribution to RT-Thread requires signing the *Contributor License Agreement*.
![Sign CLA](figures/cla.png)
Make sure that CLA shows successful signing and CI compilation, as shown in the following figure:
![CLA successful](figures/checkok.png)
Note: Do not submit commmit using a non-GitHub account, or commit using a different account, which can lead to CLA signing failure.
### Review Pull Request
Once the request is successful, the RT-Thread maintainer can see the code you submitted. The code will be reviewed and comments will be filled in on GitHub. Please check the PR status in time and update the code according to the comments.
### Merge Pull Request
If the Pull Request code is okay, the code will be merged into the RT-Thread repository. This time Pull Request succeeded.
So far, we have completed a code contribution process.
## Keep in Sync with RT-Thread Repository
The content of the RT-Thread GitHub repository is always updated. To develop based on the latest RT-Thread code, you need to update the local repository.
After clone, the local master branch content is consistent with the master branch content of the RT-Thread repository. But when the RT-Thread repository is updated, your local code is different from the RT-Thread code.
The local master is synchronized with the RT-Thread repository of your own GitHub account. If there is no content modification for the master branch (please create a new branch for development), then you can keep the local code synchronized with the RT-Thread repository according to the following steps:
- To view the existing remote repository, there is usually only one default origin, which is your own remote repository:
```c
$ git remote -v
origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
```
* Add the RT-Thread remote repository and name it `rtt`, or you can customize the name by yourself:
```c
$ git remote add rtt https://github.com/RT-Thread/rt-thread.git
```
* View all remote repositories tracked locally:
```c
$ git remote -v
origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch)
origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push)
rtt https://github.com/RT-Thread/rt-thread.git (fetch)
rtt https://github.com/RT-Thread/rt-thread.git (push)
```
* Pull the code from the master branch of RT-Thread remote repository and merge it into the local master branch:
```c
git pull rtt master
```
## Reference
* Refer to the [*GitHub - Contributing to a Project*](https://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) section of the official Git document for details.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -0,0 +1,264 @@
# ADC Device
## An Introduction to ADC
ADC refers to analog to digital converter which is a device that converts continuously changing analog signals into discrete digital signals. Analog signals , such as temperature, pressure, sound, or images, need to be converted into digital forms that are easier to be stored, processed, and transmitted. The analog-to-digital converter can do this, and it can be found in a variety of different products. The corresponding DAC (Digital-to-Analog Converter) has a reverse conversion process compared to that of the ADC. The ADC was first used to convert wireless signals to digital signals such as television signals, or signals from long-short broadcast stations.
### Conversion Process
As shown in the figure below, the analog-to-digital conversion generally involves steps of sampling, holding, quantifying, and encoding. In actual circuits, some processes are combined, such as sampling and holding, and quantization and encoding are implemented simultaneously in the conversion process.
![ADC Conversion Process](figures/adc-p.png)
Sampling is the conversion of analog signals that changes continuously over time into time-discrete analog signals. It takes a certain amount of time for the analog signals obtained by sampling to be converted into digital signals. In order to provide a stable value for the subsequent quantization coding process, it is required to keep the sampling analog signals for a period of time after the sampling circuit.
The process of converting a numerically continuous analog quantity into a digital quantity is called quantization. Digital signals are discrete numerically. The output voltage of the sample-and-hold circuit also needs to be naturalized to a corresponding discrete level in a similar way, and any digital quantity can only be an integer multiple of a certain minimum quantity unit. The quantized value also requires the encoding process, which is the digital output of the A/D converter.
### Resolution
The resolution is expressed in binary (or decimal) digits. Generally, there are 8 bits, 10 bits, 12 bits, 16 bits, etc. It explains the resolution capability of the input signals by the analog-to-digital converter. The more bits, the higher the resolution, and the more accurate the analog signal will be.
### Precision
Precision represents the maximum error value between the analog and real values of the ADC devices at all numerical points, that is, the distance at which the output value deviates from the linear maximum.
>Precision and resolution are two different concepts, so please pay attention to the distinction.
### Conversion Rate
The conversion rate is the reciprocal of the time it takes for the A/D converter to complete an AD conversion from analog to digital. For example, an A/D converter with a conversion rate of 1MHz means that an AD conversion time is 1 microsecond.
## Access ADC Device
The application accesses the ADC hardware through the ADC device management interface provided by RT-Thread. The relevant interfaces are as follows:
| **Function** | Description |
| --------------- | ------------------ |
| rt_device_find() | Find device handles based on ADC device name |
| rt_adc_enable() | Enable ADC devices |
| rt_adc_read() | Read ADC device data |
| rt_adc_disable() | Close the ADC device |
### Find ADC Devices
The application gets the device handle based on the ADC device name, which in turn operates the ADC device. Functions for looking for devices are as follows:
```c
rt_device_t rt_device_find(const char* name);
```
| **Parameter** | Description |
| -------- | ---------------------------------- |
| name | The name of the ADC device |
| **Return** | —— |
| Device handle | Finding the corresponding device will return to the corresponding device handle |
| RT_NULL | No device found |
In general, the names of the ADC device registered to the system are adc0, adc1, etc., and an usage example is as follows:
```c
#define ADC_DEV_NAME "adc1" /* ADC device name */
rt_adc_device_t adc_dev; /* ADC device handle */
/* find the device */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
```
### Enable ADC Channel
Before reading the ADC device data, Use the following function to enable the device:
```c
rt_err_t rt_adc_enable(rt_adc_device_t dev, rt_uint32_t channel);
```
| Parameter | Description |
| ---------- | ------------------------------- |
| dev | ADC device handle |
| channel | ADC channel |
| **Return** | —— |
| RT_EOK | Succeed |
| -RT_ENOSYS | Failed, the device operation method is empty |
| Other error code | Failed |
An usage example is as follows:
```c
#define ADC_DEV_NAME "adc1" /* ADC device name */
#define ADC_DEV_CHANNEL 5 /* ADC channel */
rt_adc_device_t adc_dev; /* ADC device handle */
/* find the device */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
/* enable the device */
rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
```
### Read ADC Channel Sample Values
Reading the ADC channel sample values can be done by the following function:
```c
rt_uint32_t rt_adc_read(rt_adc_device_t dev, rt_uint32_t channel);
```
| Parameter | Description |
| ---------- | ----------------- |
| dev | ADC device handle |
| channel | ADC channel |
| **Return** | —— |
| Read values | |
An example of using the ADC sampled voltage value is as follows:
```c
#define ADC_DEV_NAME "adc1" /* ADC device name */
#define ADC_DEV_CHANNEL 5 /* ADC channel */
#define REFER_VOLTAGE 330 /* Reference voltage 3.3V, data accuracy multiplied by 100 and reserve 2 decimal places*/
#define CONVERT_BITS (1 << 12) /* The number of conversion bits is 12 */
rt_adc_device_t adc_dev; /* ADC device handle */
rt_uint32_t value
/* find the device */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
/* enable the device */
rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
/* Read sampling values */
value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL);
/* Convert to the corresponding voltage value */
vol = value * REFER_VOLTAGE / CONVERT_BITS;
rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);
```
The calculation formula of the actual voltage value is: `sampling value * reference voltage/(1 << resolution digit)`. In the above example, variable *vol* was enlarged 100 times, so finally the integer part of voltage is obtained through *vol / 100*, and the decimal part of voltage is obtained through *vol % 100*.
### Close the ADC Channel
Use the following function can close the ADC channel :
```c
rt_err_t rt_adc_disable(rt_adc_device_t dev, rt_uint32_t channel);
```
| **Parameter** | **Description** |
| ---------- | ------------------------------- |
| dev | ADC device handle |
| channel | ADC channel |
| **Return** | —— |
| RT_EOK | Succeed |
| -RT_ENOSYS | Failed, the device operation method is empty |
| Other error code | Failed |
An example:
```c
#define ADC_DEV_NAME "adc1" /* ADC device name */
#define ADC_DEV_CHANNEL 5 /* ADC channel */
rt_adc_device_t adc_dev; /* ADC device handle */
rt_uint32_t value
/* find the device */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
/* enable the device */
rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
/* read sampling values */
value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL);
/* convert to the corresponding voltage value */
vol = value * REFER_VOLTAGE / CONVERT_BITS;
rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);
/* close the channel */
rt_adc_disable(adc_dev, ADC_DEV_CHANNEL);
```
### FinSH Command
Before using the device, you need to find out whether the device exists. You can use the command `adc probe` followed by the name of the registered ADC device. As follows:
```c
msh >adc probe adc1
probe adc1 success
```
A channel of the enabled device can use the command `adc enable` followed by the channel number.
```c
msh >adc enable 5
adc1 channel 5 enables success
```
To read data from a channel of an ADC device, you can use the command `adc read` followed by the channel number.
```c
msh >adc read 5
adc1 channel 5 read value is 0x00000FFF
msh >
```
To close a channel of an ADC device, you can use the command `adc disable` followed by the channel number.
```c
msh >adc disable 5
adc1 channel 5 disable success
msh >
```
## ADC Device Usage Example
The specific usage of the ADC device can refer to the following sample code. The main steps of the sample code are as follows:
1. First find the device handle based on the ADC device name “adc1”.
2. After the device is enabled, read the sample value of the corresponding channel 5 of the adc1 device, and then calculate the actual voltage value with the resolution of 12 bits and the reference voltage of 3.3V.
3. Finally close the corresponding channel of the ADC device.
Running result: Print the raw and converted data which actually read , and print the calculated actual voltage value.
```c
/*
* Program Listing ADC Device Usage Routines
* The routine exports the adc_sample command to the control terminal
* adc_sample Command call format: adc_sample
* Program function: The voltage value is sampled by the ADC device and converted to a numerical value.
* The sample code reference voltage is 3.3V and the number of conversion bits is 12 bits.
*/
#include <rtthread.h>
#include <rtdevice.h>
#define ADC_DEV_NAME "adc1" /* ADC device name */
#define ADC_DEV_CHANNEL 5 /* ADC channel */
#define REFER_VOLTAGE 330 /* Reference voltage 3.3V, data accuracy multiplied by 100 and reserve 2 decimal places*/
#define CONVERT_BITS (1 << 12) /* The number of conversion bits is 12 */
static int adc_vol_sample(int argc, char *argv[])
{
rt_adc_device_t adc_dev;
rt_uint32_t value, vol;
rt_err_t ret = RT_EOK;
/* find the device */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
if (adc_dev == RT_NULL)
{
rt_kprintf("adc sample run failed! can't find %s device!\n", ADC_DEV_NAME);
return RT_ERROR;
}
/* enable the device */
ret = rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
/* read sampling values */
value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL);
rt_kprintf("the value is :%d \n", value);
/* convert to the corresponding voltage value */
vol = value * REFER_VOLTAGE / CONVERT_BITS;
rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);
/* close the channel */
ret = rt_adc_disable(adc_dev, ADC_DEV_CHANNEL);
return ret;
}
/* export to the msh command list */
MSH_CMD_EXPORT(adc_vol_sample, adc voltage convert sample);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,486 @@
# I/O Device Framework
Most embedded systems include some I/O (Input/Output) devices, data displays on instruments, serial communication on industrial devices, Flash or SD cards for saving data on data acquisition devices,as well as Ethernet interfaces for network devices, are examples of I/O devices that are commonly seen in embedded systems.
This chapter describes how RT-Thread manages different I/O devices.
## I/O Device Introduction
### I/O Device Framework
RT-Thread provides a set of I/O device framework, as shown in the following figure. It is located between the hardware and the application. It is divided into three layers. From top to bottom, they are I/O device management layer, device driver framework layer, and device driver layer.
![I/O Device Framework](figures/io-dev.png)
The application obtains the correct device driver through the I/O device management interface, and then uses this device driver to perform data (or control) interaction with the bottom I/O hardware device.
The I/O device management layer implements the encapsulation of device drivers. The application accesses the bottom devices through the standard interface provided by the I/O device layer. The upgrade and replacement of the device driver will not affect the upper layer application. In this way, the hardware-related code of the device can exist independently of the application, and both parties only need to pay attention to the respective function implementation, thereby reducing the coupling and complexity of the code and improving the reliability of the system.
The device driver framework layer is an abstraction of the same kind of hardware device driver. The same part of the same hardware device driver of different manufacturers is extracted, and the different parts are left out of interface, implemented by the driver.
The device driver layer is a set of programs that drive the hardware devices to work, enabling access to hardware devices. It is responsible for creating and registering I/O devices. For devices with simple operation logic, you can register devices directly into the I/O Device Manager without going through the device driver framework layer. The sequence diagram is as shown below. There are mainly two points:
* The device driver creates a device instance with hardware access capabilities based on the device model definition and registers the device with the `rt_device_register()` interface in the I/O Device Manager.
* The application finds the device through the`rt_device_find()` interface and then uses the I/O device management interface to access the hardware.
![Simple I/O Device Using Sequence Diagram](figures/io-call.png)
For other devices, such as watchdog, the created device instance will be registered to the corresponding device driver framework, and then the device driver framework will register with the I/O device manager. The main points are as follows:
* The watchdog device driver creates a watchdog device instance with hardware access capability based on the watchdog device model definition and registers the watchdog device through the `rt_hw_watchdog_register()` interface into the watchdog device driver framework.
* The watchdog device driver framework registers the watchdog device to the I/O Device Manager via the `rt_device_register()` interface.
* The application accesses the watchdog device hardware through the I/O device management interface.
Usage of Watchdog device:
![Watchdog Device Use Sequence Diagram](figures/wtd-uml.png)
### I/O Device Model
The device model of RT-Thread is based on the kernel object model, which is considered a kind of objects and is included in the scope of the object manager. Each device object is derived from the base object. Each concrete device can inherit the properties of its parent class object and derive its own properties. The following figure is a schematic diagram of the inheritance and derivation relationship of device object.
![Device Inheritance Diagram](figures/io-parent.png)
The specific definitions of device objects are as follows:
```c
struct rt_device
{
struct rt_object parent; /* kernel object base class */
enum rt_device_class_type type; /* device type */
rt_uint16_t flag; /* device parameter */
rt_uint16_t open_flag; /* device open flag */
rt_uint8_t ref_count; /* number of times the device was cited */
rt_uint8_t device_id; /* device ID,0 - 255 */
/* data transceiving callback function */
rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
const struct rt_device_ops *ops; /* device operate methods */
/* device's private data */
void *user_data;
};
typedef struct rt_device *rt_device_t;
```
### I/O Device Type
RT-Thread supports multiple I/O device types, the main device types are as follows:
```c
RT_Device_Class_Char /* character device */
RT_Device_Class_Block /* block device */
RT_Device_Class_NetIf /* network interface device */
RT_Device_Class_MTD /* memory device */
RT_Device_Class_RTC /* RTC device */
RT_Device_Class_Sound /* sound device */
RT_Device_Class_Graphic /* graphic device */
RT_Device_Class_I2CBUS /* I2C bus device */
RT_Device_Class_USBDevice /* USB device */
RT_Device_Class_USBHost /* USB host device */
RT_Device_Class_SPIBUS /* SPI bus device */
RT_Device_Class_SPIDevice /* SPI device */
RT_Device_Class_SDIO /* SDIO device */
RT_Device_Class_Miscellaneous /* miscellaneous devices */
```
Character devices and block devices are commonly used device types, and their classification is based on the transmission processing between device data and the system. Character mode devices allow for unstructured data transfers, that is, data usually transfers in the form of serial, one byte at a time. Character devices are usually simple devices such as serial ports and buttons.
A block device transfers one data block at a time, for example 512 bytes data at a time. This data block is enforced by the hardware. Data blocks may use some type of data interface or some mandatory transport protocol, otherwise an error may occur. Therefore, sometimes the block device driver must perform additional work on read or write operations, as shown in the following figure:
![Block Device](figures/block-dev.png)
When the system serves a write operation with a large amount of data, the device driver must first divide the data into multiple packets, each with the data size specified by the device. In the actual process, the last part of the data size may be smaller than the normal device block size. Each block in the above figure is written to the device using a separate write request, and the first three are directly written. However, the last data block size is smaller than the device block size, and the device driver must process the last data block differently than the first 3 blocks. Normally, the device driver needs to first perform a read operation of the corresponding device block, then overwrite the write data onto the read data, and then write the "composited" data block back to the device as a whole block. . For example, for block 4 in the above figure, the driver needs to read out the device block corresponding to block 4, and then overwrite the data to be written to the data read from the device block, and merge them into a new block. Finally write back to the block device.
## Create and Register I/O Device
The driver layer is responsible for creating device instances and registering them in the I/O Device Manager. You can create device instances in a statically declared manner or dynamically create them with the following interfaces:
```c
rt_device_t rt_device_create(int type, int attach_size);
```
|**Parameters** |**Description** |
|-------------|-------------------------------------|
| type | device type, the device type values listed in "I/O Device Type" section can be used here |
| attach_size | user data size |
|**Return** | -- |
| Device Handle | Create successfully |
| RT_NULL | Creation failed, dynamic memory allocation failed |
When this interface is called, the system allocates a device control block from the dynamic heap memory, the size of which is the sum of `struct rt_device` and `attach_size`, and the type of the device is set by the parameter type. After the device is created, implementing its access to the hardware is needed.
```c
struct rt_device_ops
{
/* common device interface */
rt_err_t (*init) (rt_device_t dev);
rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
rt_err_t (*close) (rt_device_t dev);
rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
rt_err_t (*control)(rt_device_t dev, int cmd, void *args);
};
```
A description of each method of operation is shown in the following table:
|**Method Name**|**Method Description** |
|----|-----------------------|
| init | Initialize the device. After the device is initialized, the flag of the device control block is set to the active state(RT_DEVICE_FLAG_ACTIVATED). If the flag in the device control block has been set to the active state, then the initialization interface will be returned immediately when running again, and will not be re-initialized. |
| open | Open the device. Some devices are not started when the system is started, or the device needs to send and receive data. However, if the upper application is not ready, the device should not be enabled by default and start receiving data. Therefore, it is recommended to enable the device when calling the open interface when writing the bottom driver. |
| close | Close the device. When the device is open, the device control block maintains an open count, the count will add 1 when the device is opended, and the count will subtract 1 when the device is closed, and a real shutdown operation is operated when the counter turns to 0. |
| read | Read data from the device. The parameter pos is the offset of the read data, but some devices do not necessarily need to specify the offset, such as serial devices, the device driver should ignore this parameter. But for block devices, pos and size are measured in the block size of the block device. For example, the block size of the block device is 512 byte, and in the parameter pos = 10, size = 2, then the driver should return the 10th block in the device (starting from the 0th block) for a total of 2 blocks of data. The type returned by this interface is rt_size_t, which is the number of bytes read or the number of blocks. Normally, the value of size in the parameter should be returned. If it returns zero, set the corresponding errno value. |
| write | Write data to the device. The parameter pos is the offset of the write data. Similar to read operations, for block devices, pos and size are measured in the block size of the block device. The type returned by this interface is rt_size_t, which is the number of bytes or blocks of data actually written. Normally, the value of size in the parameter should be returned. If it returns zero, set the corresponding errno value. |
| control | Control the device according to the cmd command. Commands are often implemented by the bottom device drivers. For example, the parameter RT_DEVICE_CTRL_BLK_GETGEOME means to get the size information of the block device. |
When a dynamically created device is no longer needed, it can be destroyed using the following function:
```c
void rt_device_destroy(rt_device_t device);
```
|**Parameters**|**Description**|
|----------|----------|
| device | device handle |
After the device is created, it needs to be registered to the I/O Device Manager for the application to access. The functions for registering the device are as follows:
```c
rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);
```
|**Parameters** |**Description** |
|------------|-----------------------|
| dev | device handle |
| name | device name, the maximum length of the device name is specified by the macro RT_NAME_MAX defined in rtconfig.h, and the extra part is automatically truncated |
| flags | device mode flag |
|**Return** | -- |
| RT_EOK | registration success |
| -RT_ERROR | registration failed, dev is empty or name already exists |
>It should be avoided to repeatedly register registered devices and to register devices with the same name.
flags parameters support the following parameters (multiple parameters can be supported in OR logic):
```c
#define RT_DEVICE_FLAG_RDONLY 0x001 /* read only */
#define RT_DEVICE_FLAG_WRONLY 0x002 /* write only */
#define RT_DEVICE_FLAG_RDWR 0x003 /* read and write */
#define RT_DEVICE_FLAG_REMOVABLE 0x004 /* can be removed */
#define RT_DEVICE_FLAG_STANDALONE 0x008 /* stand alone */
#define RT_DEVICE_FLAG_SUSPENDED 0x020 /* suspended */
#define RT_DEVICE_FLAG_STREAM 0x040 /* stream mode */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* interrupt reception */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA reception */
#define RT_DEVICE_FLAG_INT_TX 0x400 /* interrupt sending */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA sending */
```
Device Stream Mode The RT_DEVICE_FLAG_STREAM parameter is used to output a character string to the serial terminal: when the output character is `\n` , it automatically fills in a `\r` to make a branch.
Successfully registered devices can use the `list_device` command on the FinSH command line to view all device information in the system, including the device name, device type, and number of times the device is opened:
```c
msh />list_device
device type ref count
-------- -------------------- ----------
e0 Network Interface 0
sd0 Block Device 1
rtc RTC 0
uart1 Character Device 0
uart0 Character Device 2
msh />
```
When the device is logged off, the device will be removed from the device manager and the device will no longer be found through the device. Logging out of the device does not release the memory occupied by the device control block. The function to log off of the device is as follows:
```c
rt_err_t rt_device_unregister(rt_device_t dev);
```
|**Parameters**|**Description**|
|----------|----------|
| dev | device handle |
|**Return**| -- |
| RT_EOK | successful |
The following code is an example of registering a watchdog device. After calling the `rt_hw_watchdog_register()` interface, the device is registered to the I/O Device Manager via the `rt_device_register()` interface.
```c
const static struct rt_device_ops wdt_ops =
{
rt_watchdog_init,
rt_watchdog_open,
rt_watchdog_close,
RT_NULL,
RT_NULL,
rt_watchdog_control,
};
rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd,
const char *name,
rt_uint32_t flag,
void *data)
{
struct rt_device *device;
RT_ASSERT(wtd != RT_NULL);
device = &(wtd->parent);
device->type = RT_Device_Class_Miscellaneous;
device->rx_indicate = RT_NULL;
device->tx_complete = RT_NULL;
device->ops = &wdt_ops;
device->user_data = data;
/* register a character device */
return rt_device_register(device, name, flag);
}
```
## Access I/O Devices
The application accesses the hardware device through the I/O device management interface, which is accessible to the application when the device driver is implemented. The mapping relationship between the I/O device management interface and the operations on the I/O device is as follows:
![Mapping between the I/O Device Management Interface and the Operations on the I/O Device](figures/io-fun-call.png)
### Find Device
The application obtains the device handle based on the device name, which in turn allows the device to operate. To find device, use function below:
```c
rt_device_t rt_device_find(const char* name);
```
|**Parameters**|**Description** |
|----------|------------------------------------|
| name | device name |
|**Return**| -- |
| device handle | finding the corresponding device will return the corresponding device handle |
| RT_NULL | no corresponding device object found |
### Initialize Device
Once the device handle is obtained, the application can initialize the device using the following functions:
```c
rt_err_t rt_device_init(rt_device_t dev);
```
|**Parameters**|**Description** |
|----------|----------------|
| dev | device handle |
|**Return**| -- |
| RT_EOK | device initialization succeeded |
| Error Code | device initialization failed |
>When a device has been successfully initialized, calling this interface will not repeat initialization.
### Open and Close Device
Through the device handle, the application can open and close the device. When the device is opened, it will detect whether the device has been initialized. If it is not initialized, it will call the initialization interface to initialize the device by default. Open the device with the following function:
```c
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
```
|**Parameters** |**Description** |
|------------|-----------------------------|
| dev | device handle |
| oflags | open device in oflag mode |
|**Return** | -- |
| RT_EOK | device successfully turned on |
|-RT_EBUSY | device will not allow being repeated opened if the RT_DEVICE_FLAG_STANDALONE parameter is included in the parameters specified when the device is registered. |
| Other Error Code | device failed to be turned on |
oflags supports the following parameters:
```c
#define RT_DEVICE_OFLAG_CLOSE 0x000 /* device was already closed(internal use)*/
#define RT_DEVICE_OFLAG_RDONLY 0x001 /* open the device in read-only mode */
#define RT_DEVICE_OFLAG_WRONLY 0x002 /* open the device in write-only mode */
#define RT_DEVICE_OFLAG_RDWR 0x003 /* open the device in read-and_write mode */
#define RT_DEVICE_OFLAG_OPEN 0x008 /* device was already closed(internal use) */
#define RT_DEVICE_FLAG_STREAM 0x040 /* open the device in stream mode */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* open the device in interrupt reception mode */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /* open the device in DMA mode */
#define RT_DEVICE_FLAG_INT_TX 0x400 /* open the device in interrupt sending mode */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /* open the device in DMA mode */
```
>If the upper application needs to set the device's receive callback function, it must open the device as RT_DEVICE_FLAG_INT_RX or RT_DEVICE_FLAG_DMA_RX, otherwise the callback function will not be called.
After the application opens the device to complete reading and writing, if no further operations are needed, you can close the device using the following functions:
```c
rt_err_t rt_device_close(rt_device_t dev);
```
|**Parameters** |**Description** |
|------------|------------------------------------|
| dev | device handle |
|**Return** | -- |
| RT_EOK | device successfully closed |
| \-RT_ERROR | device has been completely closed and cannot be closed repeatedly |
| Other Error Code | failed to close device |
>Device interfaces `rt_device_open()` and `rt_device_close()` need to used in pairs. Open a device requires close the device, so that the device will be completely closed, otherwise the device will remain on.
### Control Device
By commanding the control word, the application can also control the device with the following function:
```c
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
```
|**Parameters** |**Description** |
|-------------|--------------------------------------------|
| dev | device handle |
| cmd | command control word, this parameter is usually related to the device driver |
| arg | controlled parameter |
|**Return** | -- |
| RT_EOK | function executed successfully |
| -RT_ENOSYS | execution failed, dev is empty |
| Other Error Code | execution failed |
The generic device command for the parameter `cmd` can be defined as follows:
```c
#define RT_DEVICE_CTRL_RESUME 0x01 /* resume device */
#define RT_DEVICE_CTRL_SUSPEND 0x02 /* suspend device */
#define RT_DEVICE_CTRL_CONFIG 0x03 /* configure device */
#define RT_DEVICE_CTRL_SET_INT 0x10 /* set interrupt */
#define RT_DEVICE_CTRL_CLR_INT 0x11 /* clear interrupt */
#define RT_DEVICE_CTRL_GET_INT 0x12 /* obtain interrupt status */
```
### Read and Write Device
Application can read data from the device by the following function:
```c
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
```
|**Parameters** |**Description** |
|--------------------|--------------------------------|
| dev | device handle |
| pos | read data offset |
| buffer | memory buffer pointer, the data read will be saved in the buffer |
| size | size of the data read |
|**Return** | -- |
| Actual Size of the Data Read | If it is a character device, the return size is in bytes. If it is a block device, the returned size is in block units. |
| 0 | need to read the current thread's errno to determine the error status |
Calling this function will read the data from the dev device and store it in the buffer. The maximum length of this buffer is *size*, and *pos* has different meanings depending on the device class.
Writing data to the device can be done by the following function:
```c
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);
```
|**Parameters** |**Description** |
|--------------------|--------------------------------|
| dev | device handle |
| pos | write data offset |
| buffer | memory buffer pointer, placing the data to be written in |
| size | size of the written data |
|**Return** | -- |
| Actual Size of the Data Written | If it is a character device, the return size is in bytes. If it is a block device, the returned size is in block units. |
| 0 | need to read the current thread's errno to determine the error status |
Calling this function will write the data in the buffer to the *dev* device . The maximum length of the written data is *size*, and *pos* has different meanings depending on the device class.
### Data Transceiving and Call-back
When the hardware device receives the data, the following function can be used to call back another function to set the data receiving indication to notify the upper application thread that the data arrives:
```c
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
```
|**Parameters**|**Description** |
|----------|--------------|
| dev | device handle |
| rx_ind | callback function pointer |
|**Return**| -- |
| RT_EOK | set successfully |
The callback of this function will be provided by the user. When the hardware device receives the data, it will perform the callback function and pass the received data length to the upper layer application in the *size* parameter. The upper application thread should read the data from the device as soon as it receives the indication.
When the application calls `rt_device_write()` to write data, if the bottom hardware can support automatic sending, the upper application can set a callback function. This callback function is called after the bottom hardware data has been sent (for example, when the DMA transfer is complete or the FIFO has been written to complete, triggered interrupt). Use the following function to set the device with a send completion indication. The function parameters and return values are as follows:
```c
rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
```
|**Parameters**|**Description** |
|----------|--------------|
| dev | device handle |
| tx_done | callback function pointer |
|**Return**| -- |
| RT_EOK | set successfully |
When this function is called, the callback function is provided by the user. When the hardware device sends the data, the driver calls back the function and passes the sent data block address buffer as a parameter to the upper application. When the upper layer application (thread) receives the indication, it will release the buffer memory block or use it as the buffer for the next write data according to the condition of sending the buffer.
### Access Device Sample
The following code is an example of accessing a device. First, find the watchdog device through the `rt_device_find()` port, obtain the device handle, then initialize the device through the `rt_device_init()` port, and set the watchdog device timeout through the `rt_device_control()`port.
```c
#include <rtthread.h>
#include <rtdevice.h>
#define IWDG_DEVICE_NAME "iwg"
static rt_device_t wdg_dev;
static void idle_hook(void)
{
/* feed the dog in the callback function of the idle thread */
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
rt_kprintf("feed the dog!\n ");
}
int main(void)
{
rt_err_t res = RT_EOK;
rt_uint32_t timeout = 1000; /* timeout */
/* find the watchdog device based on the device name, and obtain the device handle */
wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
if (!wdg_dev)
{
rt_kprintf("find %s failed!\n", IWDG_DEVICE_NAME);
return RT_ERROR;
}
/* initialize device */
res = rt_device_init(wdg_dev);
if (res != RT_EOK)
{
rt_kprintf("initialize %s failed!\n", IWDG_DEVICE_NAME);
return res;
}
/* set watchdog timeout */
res = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
if (res != RT_EOK)
{
rt_kprintf("set %s timeout failed!\n", IWDG_DEVICE_NAME);
return res;
}
/* set idle thread callback function */
rt_thread_idle_sethook(idle_hook);
return res;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,403 @@
# HWTIMER Device
## Introduction to the Timer
Hardware timers generally have two modes of operation, timer mode and counter mode. No matter which mode is operated, it works by counting the pulse signal counted by the internal counter module. Here are some important concepts of timers.
**Counter mode:** Counts the external pulse.
**Timer mode **: Counts the internal pulse. Timers are often used as timing clocks for timing detection, timing response, and timing control.
**Counter **: Counter can count up or down. The maximum count value of the 16-bit counter is 65535, and the maximum value of the 32-bit counter is 4294967295.
**Counting frequency **As for the number of counts within the counter time unit under the timer mode, since the system clock frequency is fixed, the timer time can be calculated according to the counter count value. `Timing time = count value / count frequency`. For example, if the counting frequency is 1 MHz, the counter counts once is 1 / 1000000 second. That is, every 1 microsecond counter is incremented by one (or subtract one), at this time, the maximum timing capability of the 16-bit counter is 65535 microseconds, which is 65.535 milliseconds.
## Access Hardware Timer Device
The application accesses the hardware timer device through the I/O device management interface provided by RT-Thread. The related interfaces are as follows:
| **Function** | **Description** |
| -------------------- | ---------------------------------- |
| rt_device_find() | to look up the timer device |
| rt_device_open() | to open the timer device in read-write mode |
| rt_device_set_rx_indicate() | to set the timeout callback function |
| rt_device_control() | to control the timer device, you can set the timing mode (single time /cycle),counting frequency, or stop the timer |
| rt_device_write() | to set the timeout value of the timer. The timer then starts |
| rt_device_read() | to get the current value of the timer |
| rt_device_close() | to turn off the timer device. |
### Find Timer Device
The application obtains the device handle based on the hardware timer device name, and thus can operate the hardware timer device. The device function is as follows:
```c
rt_device_t rt_device_find(const char* name);
```
| Parameter | **Description** |
| -------- | ---------------------------------- |
| name | hardware timer device name |
| **return** | —— |
| timer device handle | will return to the corresponding device handle if the corresponding device is found |
| RT_NULL | No device found |
In general, the hardware timer device name registered to the system is timer0, timer1, etc. The usage examples are as follows:
```c
#define HWTIMER_DEV_NAME "timer0" /* timer name */
rt_device_t hw_dev; /* timer device handle */
/* find timer device */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
```
### Open Timer Device
With the device handle, the application can open the device. When the device is open, it will detect whether the device has been initialized. If it is not initialized, it will call the initialization interface to initialize the device by default. Open the device with the following function:
```c
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
```
| Parameter | Description |
| ---------- | ------------------------------- |
| dev | hardware timer device handle |
| oflags | device open mode, is generally opened in read and write mode, which is to take the valueRT_DEVICE_OFLAG_RDWR |
| **return** | —— |
| RT_EOK | device opened successfully |
| other error code | device fail to open |
An example of use is as follows:
```c
#define HWTIMER_DEV_NAME "timer0" /* timer name */
rt_device_t hw_dev; /* timer device handle */
/* find timer device */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
/* to open the timer device in read-write mode */
rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
```
### Set the Timeout Callback Function
Set the timer timeout callback function by the following function, this callback function will be called when the timer expires:
```c
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size))
```
| Parameter | **Description** |
| ---------- | ------------------------------- |
| dev | device handle |
| rx_ind | timeout callback function, provided by the caller |
| **return** | —— |
| RT_EOK | success |
An example of use is as follows
```c
#define HWTIMER_DEV_NAME "timer0" /* timer name */
rt_device_t hw_dev; /* timer device handle */
/* timer timeout callback function */
static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size)
{
rt_kprintf("this is hwtimer timeout callback fucntion!\n");
rt_kprintf("tick is :%d !\n", rt_tick_get());
return 0;
}
static int hwtimer_sample(int argc, char *argv[])
{
/* find timer device */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
/* open the device in read and write mode */
rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
/* set the timeout callback function */
rt_device_set_rx_indicate(hw_dev, timeout_cb);
}
```
### Control the Timer Device
By commanding the control word, the application can configure the hardware timer device by the following function:
```c
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
```
| Parameter | **Description** |
| ---------------- | ------------------------------ |
| dev | device handle |
| cmd | command control word |
| arg | controlled parameter |
| **return** | —— |
| RT_EOK | function executed successfully |
| -RT_ENOSYS | execution faileddev is null |
| other error code | execution failed |
The command control words available for the hardware timer device are as follows
| **Control word** | Description |
| ---------------------- | ------------------------ |
| HWTIMER_CTRL_FREQ_SET | set the counting frequency |
| HWTIMER_CTRL_STOP | stop the timer |
| HWTIMER_CTRL_INFO_GET | get timer feature information |
| HWTIMER_CTRL_MODE_SET | set timer mode |
Get the timer parameter arg,which is a pointer to the structure struct rt_hwtimer_info, to save the obtained information.
>Setting frequency is valid only when the timer hardware and driver support sets the counting frequency. Generally, the default frequency of the driving setting can be used.
When setting the timer mode, the parameter arg can take the following values
```c
HWTIMER_MODE_ONESHOT /* Single timing */
HWTIMER_MODE_PERIOD /* Periodic timing */
```
An example of using the timer count frequency and timing mode is as follows:
```c
#define HWTIMER_DEV_NAME "timer0" /* timer name */
rt_device_t hw_dev; /* timer device handle */
rt_hwtimer_mode_t mode; /* timer mode */
rt_uint32_t freq = 10000; /* couting frequency */
/* Timer timeout callback function */
static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size)
{
rt_kprintf("this is hwtimer timeout callback fucntion!\n");
rt_kprintf("tick is :%d !\n", rt_tick_get());
return 0;
}
static int hwtimer_sample(int argc, char *argv[])
{
/* find timer device */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
/* open the device in read and write mode */
rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
/* Set the timeout callback function */
rt_device_set_rx_indicate(hw_dev, timeout_cb);
/* Set the counting frequency (1Mhz or the supported minimum counting frequency by default) */
rt_device_control(hw_dev, HWTIMER_CTRL_FREQ_SET, &freq);
/* Set the mode to periodic timer */
mode = HWTIMER_MODE_PERIOD;
rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode);
}
```
### Set the Timer Timeout Value
The timer timeout value can be set by the following function
```c
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
```
| **Parameter** | Description |
| ---------- | ------------------------------------------ |
| dev | device handle |
| pos | write data offset, unused now, can set 0 value |
| buffer | pointer to the timer timeout structure |
| size | timeout structure size |
| **return** | —— |
| The actual size of the written data | |
| 0 | fail |
The prototype of the timeout structure is shown below :
```c
typedef struct rt_hwtimerval
{
rt_int32_t sec; /* second */
rt_int32_t usec; /* microsecond */
} rt_hwtimerval_t;
```
An example of using the timer timeout value is as follows:
```c
#define HWTIMER_DEV_NAME "timer0" /* timer name */
rt_device_t hw_dev; /* timer device handle */
rt_hwtimer_mode_t mode; /* timer mode */
rt_hwtimerval_t timeout_s; /* Timer timeout value */
/* Timer timeout callback function */
static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size)
{
rt_kprintf("this is hwtimer timeout callback fucntion!\n");
rt_kprintf("tick is :%d !\n", rt_tick_get());
return 0;
}
static int hwtimer_sample(int argc, char *argv[])
{
/* find timer device */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
/* open the device in read-write mode */
rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
/* set the timeout callback function */
rt_device_set_rx_indicate(hw_dev, timeout_cb);
/* set the mode as periodic timer */
mode = HWTIMER_MODE_PERIOD;
rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode);
/* Set the timer timeout value to 5s and start the timer */
timeout_s.sec = 5; /* second */
timeout_s.usec = 0; /* microsecond */
rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s));
}
```
### Obtain the Current Value of the Timer
The current value of the timer can be obtained by the following function
```c
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
```
| **Parameter** | Description |
| ---------- | ------------------------------------------ |
| dev | timer device handle |
| pos | write data offset, unused now , can set 0 value |
| buffer | output parameter, a pointer point to the timeout structure |
| size | timeout structure size |
| **return** | —— |
| Timeout structure size | success |
| 0 | fail |
An example of use is shown below
```c
rt_hwtimerval_t timeout_s; /* Used to save the time the timer has elapsed */
/* Read the elapsed time of the timer */
rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s));
```
### Close the Timer Device
The timer device can be closed by the following function:
```c
rt_err_t rt_device_close(rt_device_t dev);
```
| Parameter | Description |
| ---------- | ---------------------------------- |
| dev | timer device handle |
| **return** | —— |
| RT_EOK | close device successfully |
| -RT_ERROR | the device has been completely shut down and cannot be closed repeatedly |
| other error code | fail to close the device |
To close the device interface and open the device interface should be used in pairs. When open a device, close the device after use , so that the device can be completely shut down, otherwise the device will remain a opening status.
An example of use is shown below
```c
#define HWTIMER_DEV_NAME "timer0" /* timer name */
rt_device_t hw_dev; /* timer device handle */
/* find timer device */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
... ...
rt_device_close(hw_dev);
```
>Timing errors may occur. Assume that the counter has a maximum value of 0xFFFF, a counting frequency of 1Mhz, and a timing time of 1 second and 1 microsecond. Since the timer can only count up to 65535us at a time, the timing requirement for 1000001us can be completed 20 times at 50000us, and the calculation error will be 1us.
## Hardware Timer Device Usage Example
The specific use of the hardware timer device can refer to the following sample code. The main steps of the sample code are as follows:
1. First find the device handle based on the timer device name "timer0".
2. Open the device "timer0" in read-write mode.
3. Set the timer timeout callback function.
4. Set the timer mode to periodic timer and set the timeout period to 5 seconds. At this time, the timer starts.
5. Read the timer after 3500ms delay, the read value will be displayed in seconds and microseconds.
```c
/*
* Program listing: This is an hwtimer device usage routine
  * The routine exports the hwtimer_sample command to the control terminal
  * Command call format: hwtimer_sample
  * Program function: The hardware timer timeout callback function periodically prints the current tick value, and the difference between the two tick values is converted to the time equivalent to the timing time value.
*/
#include <rtthread.h>
#include <rtdevice.h>
#define HWTIMER_DEV_NAME "timer0" /* timer name */
/* Timer timeout callback function */
static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size)
{
rt_kprintf("this is hwtimer timeout callback fucntion!\n");
rt_kprintf("tick is :%d !\n", rt_tick_get());
return 0;
}
static int hwtimer_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
rt_hwtimerval_t timeout_s; /* timer timeout value */
rt_device_t hw_dev = RT_NULL; /* timer device value */
rt_hwtimer_mode_t mode; /* timer mode */
/* find timer device */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
if (hw_dev == RT_NULL)
{
rt_kprintf("hwtimer sample run failed! can't find %s device!\n", HWTIMER_DEV_NAME);
return RT_ERROR;
}
/* Open the device in read-write mode */
ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
if (ret != RT_EOK)
{
rt_kprintf("open %s device failed!\n", HWTIMER_DEV_NAME);
return ret;
}
/* set timeout callback function */
rt_device_set_rx_indicate(hw_dev, timeout_cb);
/* Setting mode is periodic timer */
mode = HWTIMER_MODE_PERIOD;
ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode);
if (ret != RT_EOK)
{
rt_kprintf("set mode failed! ret is :%d\n", ret);
return ret;
}
/* Set the timer timeout value to 5s and start the timer. */
timeout_s.sec = 5; /* second */
timeout_s.usec = 0; /* microsecond */
if (rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s))
{
rt_kprintf("set timeout value failed\n");
return RT_ERROR;
}
/* delay 3500ms */
rt_thread_mdelay(3500);
/* read the current value of timer */
rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s));
rt_kprintf("Read: Sec = %d, Usec = %d\n", timeout_s.sec, timeout_s.usec);
return ret;
}
/* Export to the msh command list */
MSH_CMD_EXPORT(hwtimer_sample, hwtimer sample);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,298 @@
# I2C Bus Device
## Introduction of I2C
The I2C (Inter Integrated Circuit) bus is a half-duplex, bidirectional two-wire synchronous serial bus developed by PHILIPS. The I2C bus has only two signal lines, one is the bidirectional data line SDA (serial data), and the other is the bidirectional clock line SCL (serial clock). The SPI bus has two lines for receiving data and transmitting data between the master and slave devices, while the I2C bus uses only one line for data transmission and reception.
Like SPI, I2C works in a master-slave manner. Unlike SPI-master-multi-slave architecture, it allows multiple master devices to exist at the same time. Each device connected to the bus has a unique address, and the master device initiates data transfer, and generates a clock signal. The slave device is addressed by the master device, and only one master device is allowed at a time. As shown below:
![I2C Bus master-slave device connection mode](figures/i2c1.png)
The main data transmission format of the I2C bus is shown in the following figure:
![I2C Bus Data Transmission Format](figures/i2c2.png)
When the bus is idle, both SDA and SCL are in a high state. When the host wants to communicate with a slave, it will send a start condition first, then send the slave address and read and write control bits, and then transfer the data (host send or receive data). The host will send a stop condition when the data transfer ends. Each byte transmitted is 8 bits, with the high bit first and the low bit last. The different terms in the data transmission process are as follows:
* **Starting Condition** When SCL is high, the host pulls SDA low, indicating that data transfer is about to begin.
* **Slave Address** The first byte sent by the master is the slave address, the upper 7 bits are the address, the lowest bit is the R/W read/write control bit, R/W bit equals to 1 means the read operation, and 0 means the write operation. The general slave address has 7-bit address mode and 10-bit address mode. In the 10-bit address mode, the first 7 bits of the first byte are a combination of 11110XX, where the last two bits (XX) are two highest 10-bit addresses. The second byte is the remaining 8 bits of the 10-bit slave address, as shown in the following figure:
![7-bit address and 10-bit address format](figures/i2c3.png)
* **Answer Signal** Each time a byte of data is transmitted, the receiver needs to reply with an ACK (acknowledge). The slave sends an ACK when writing data and the ACK by the host when reading data. When the host reads the last byte of data, it can send NACK (Not acknowledge) and then stop the condition.
* **Data** After the slave address is sent, some commands may be sent, depending on the slave, and then the data transmission starts, and is sent by the master or the slave. Each data is 8 bits, and the number of bytes of data is not limited.
* **Repeat Start Condition** In a communication process, when the host may need to transfer data with different slaves or need to switch read and write operations, the host can send another start condition.
* **Stop Condition** When SDA is low, the master pulls SCL high and stays high, then pulls SDA high to indicate the end of the transfer.
## Access to I2C Bus Devices
In general, the MCU's I2C device communicates as a master and slave. In the RT-Thread, the I2C master is virtualized as an I2C bus device. The I2C slave communicates with the I2C bus through the I2C device interface. The related interfaces are as follows:
| **Function** | **Description** |
| --------------- | ---------------------------------- |
| rt_device_find() | Find device handles based on I2C bus device name |
| rt_i2c_transfer() | transfer data |
### Finding I2C Bus Device
Before using the I2C bus device, you need to obtain the device handle according to the I2C bus device name, so that you can operate the I2C bus device. The device function is as follows.
```c
rt_device_t rt_device_find(const char* name);
```
| Parameter | Description |
| -------- | ---------------------------------- |
| name | I2C bus device name |
| **Return Value** | —— |
| device handle | Finding the corresponding device will return the corresponding device handle |
| RT_NULL | No corresponding device object found |
In general, the name of the I2C device registered to the system is i2c0, i2c1, etc. The usage examples are as follows:
```c
#define AHT10_I2C_BUS_NAME "i2c1" /* Sensor connected I2C bus device name */
struct rt_i2c_bus_device *i2c_bus; /* I2C bus device handle */
/* Find the I2C bus device and get the I2C bus device handle */
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);
```
### Data Transmission
You can use `rt_i2c_transfer()` for data transfer by getting the I2C bus device handle. The function prototype is as follows:
```c
rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg msgs[],
rt_uint32_t num);
```
| Parameter | Description |
|--------------------|----------------------|
| bus | I2C bus device handle |
| msgs[] | Message array pointer to be transmitted |
| num | The number of elements in the message array |
| **Return Value** | —— |
| the number of elements in the message array | succeeded |
| error code | failed |
Like the custom transport interface of the SPI bus, the data transmitted by the custom transport interface of the I2C bus is also in units of one message. The parameter msgs[] points to the array of messages to be transmitted. The user can customize the content of each message to implement two different data transmission modes supported by the I2C bus. If the master needs to send a repeat start condition, it will need to send 2 messages.
>This function will call rt_mutex_take(), which cannot be called inside the interrupt service routine, which will cause assertion to report an error.
The prototypes of the I2C message data structure are as follows:
```c
struct rt_i2c_msg
{
rt_uint16_t addr; /* Slave address */
rt_uint16_t flags; /* Reading, writing signs, etc. */
rt_uint16_t len; /* Read and write data bytes */
rt_uint8_t *buf; /* Read and write data buffer pointer */
}
```
Slave address (addr): Supports 7-bit and 10-bit binary addresses. You need to view the data sheets of different devices.
>The slave address used by the RT-Thread I2C device interface does not contain read/write bits. The read/write bit control needs to modify the flag `flags`.
The flags `flags` can be defined as macros that can be combined with other macros using the bitwise operation "|" as needed.
```c
#define RT_I2C_WR 0x0000 /* Write flag */
#define RT_I2C_RD (1u << 0) /* Read flag */
#define RT_I2C_ADDR_10BIT (1u << 2) /* 10-bit address mode */
#define RT_I2C_NO_START (1u << 4) /* No start condition */
#define RT_I2C_IGNORE_NACK (1u << 5) /* Ignore NACK */
#define RT_I2C_NO_READ_ACK (1u << 6) /* Do not send ACK when reading */
```
Examples of use are as follows:
```c
#define AHT10_I2C_BUS_NAME "i2c1" /* Sensor connected I2C bus device name */
#define AHT10_ADDR 0x38 /* Slave address */
struct rt_i2c_bus_device *i2c_bus; /* I2C bus device handle */
/* Find the I2C bus device and get the I2C bus device handle */
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);
/* Read sensor register data */
static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf)
{
struct rt_i2c_msg msgs;
msgs.addr = AHT10_ADDR; /* Slave address */
msgs.flags = RT_I2C_RD; /* Read flag */
msgs.buf = buf; /* Read and write data buffer pointer */
msgs.len = len; /* Read and write data bytes */
/* Call the I2C device interface to transfer data */
if (rt_i2c_transfer(bus, &msgs, 1) == 1)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
```
## I2C Bus Device Usage Example
The specific usage of the I2C device can be referred to the following sample code. The main steps of the sample code are as follows:
1. First find the I2C name based on the I2C device name, get the device handle, and then initialize the aht10 sensor.
2. The two functions that control the sensor are the write sensor register `write_reg()` and the read sensor register `read_regs()`, both called `rt_i2c_transfer()` to transfer the data. The function `read_temp_humi()` calls the above two functions to read the temperature and humidity information.
```c
/*
* Program listing: This is an I2C device usage routine
* The routine exports the i2c_aht10_sample command to the control terminal
* Command call format: i2c_aht10_sample i2c1
* Command explanation: The second parameter of the command is the name of the I2C bus device to be used. If it is empty, the default I2C bus device is used.
* Program function: read the temperature and humidity data of the aht10 sensor and print.
*/
#include <rtthread.h>
#include <rtdevice.h>
#define AHT10_I2C_BUS_NAME "i2c1" /* Sensor connected I2C bus device name */
#define AHT10_ADDR 0x38 /* Slave address */
#define AHT10_CALIBRATION_CMD 0xE1 /* Calibration command */
#define AHT10_NORMAL_CMD 0xA8 /* General command */
#define AHT10_GET_DATA 0xAC /* Get data command */
static struct rt_i2c_bus_device *i2c_bus = RT_NULL; /* I2C bus device handle */
static rt_bool_t initialized = RT_FALSE; /* Sensor initialization status */
/* Write sensor register */
static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t *data)
{
rt_uint8_t buf[3];
struct rt_i2c_msg msgs;
buf[0] = reg; //cmd
buf[1] = data[0];
buf[2] = data[1];
msgs.addr = AHT10_ADDR;
msgs.flags = RT_I2C_WR;
msgs.buf = buf;
msgs.len = 3;
/* Call the I2C device interface to transfer data */
if (rt_i2c_transfer(bus, &msgs, 1) == 1)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
/* Read sensor register data */
static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf)
{
struct rt_i2c_msg msgs;
msgs.addr = AHT10_ADDR;
msgs.flags = RT_I2C_RD;
msgs.buf = buf;
msgs.len = len;
/* Call the I2C device interface to transfer data */
if (rt_i2c_transfer(bus, &msgs, 1) == 1)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
static void read_temp_humi(float *cur_temp, float *cur_humi)
{
rt_uint8_t temp[6];
write_reg(i2c_bus, AHT10_GET_DATA, 0); /* send command */
rt_thread_mdelay(400);
read_regs(i2c_bus, 6, temp); /* obtian sensor data */
/* Humidity data conversion */
*cur_humi = (temp[1] << 12 | temp[2] << 4 | (temp[3] & 0xf0) >> 4) * 100.0 / (1 << 20);
/* Temperature data conversion */
*cur_temp = ((temp[3] & 0xf) << 16 | temp[4] << 8 | temp[5]) * 200.0 / (1 << 20) - 50;
}
static void aht10_init(const char *name)
{
rt_uint8_t temp[2] = {0, 0};
/* Find the I2C bus device and get the I2C bus device handle */
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);
if (i2c_bus == RT_NULL)
{
rt_kprintf("can't find %s device!\n", name);
}
else
{
write_reg(i2c_bus, AHT10_NORMAL_CMD, temp);
rt_thread_mdelay(400);
temp[0] = 0x08;
temp[1] = 0x00;
write_reg(i2c_bus, AHT10_CALIBRATION_CMD, temp);
rt_thread_mdelay(400);
initialized = RT_TRUE;
}
}
static void i2c_aht10_sample(int argc, char *argv[])
{
float humidity, temperature;
char name[RT_NAME_MAX];
humidity = 0.0;
temperature = 0.0;
if (argc == 2)
{
rt_strncpy(name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(name, AHT10_I2C_BUS_NAME, RT_NAME_MAX);
}
if (!initialized)
{
/* Sensor initialization */
aht10_init(name);
}
if (initialized)
{
/* Read temperature and humidity data */
read_temp_humi(&temperature, &humidity);
rt_kprintf("read aht10 sensor humidity : %d.%d %%\n", (int)humidity, (int)(humidity * 10) % 10);
rt_kprintf("read aht10 sensor temperature: %d.%d \n", (int)temperature, (int)(temperature * 10) % 10);
}
else
{
rt_kprintf("initialize sensor failed!\n");
}
}
/* Export to the msh command list */
MSH_CMD_EXPORT(i2c_aht10_sample, i2c aht10 sample);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,353 @@
# PIN Device
## Introduction of Pin
The pins on the chip are generally divided into four categories: power supply, clock, control, and I/O. The I/O port is further divided into General Purpose Input Output (GPIO) and function multiplex I/O (such as SPI/I2C/UART, etc.) in the usage mode.
Most MCU pins have more than one function. The internal structure of different pins is different and the functions are different. The actual function of the pin can be switched through different configurations. The main features of the General Purpose Input Output (GPIO) port are as follows:
* Programmable Interrupt: The interrupt trigger mode is configurable. Generally, there are five interrupt trigger modes as shown in the following figure:
![5 Interrupt Trigger Modes](figures/pin2.png)
* Input and output modes can be controlled.
* Output modes generally include Output push-pull, Output open-drain, Output pull-up, and Output pull-down. When the pin is in the output mode, the connected peripherals can be controlled by configuring the level of the pin output to be high or low.
* Input modes generally include: Input floating, Input pull-up, Input pull-down, and Analog. When the pin is in the input mode, the level state of the pin can be read, that is, high level or low level.
## Access PIN Device
The application accesses the GPIO through the PIN device management interface provided by RT-Thread. The related interfaces are as follows:
| Function | **Description** |
| ---------------- | ---------------------------------- |
| rt_pin_mode() | Set pin mode |
| rt_pin_write() | Set the pin level |
| rt_pin_read() | Read pin level |
| rt_pin_attach_irq() | Bind pin interrupt callback function |
| rt_pin_irq_enable() | Enable pin interrupt |
| rt_pin_detach_irq() | Detach pin interrupt callback function |
### Obtain Pin Number
The pin numbers provided by RT-Thread need to be distinguished from the chip pin numbers. They are not the same concept. The pin numbers are defined by the PIN device driver and are related to the specific chip. There are two ways to obtain the pin number: use the macro definition or view the PIN driver file.
#### Use Macro Definition
If you use the BSP in the `rt-thread/bsp/stm32` directory, you can use the following macro to obtain the pin number:
```c
GET_PIN(port, pin)
```
The sample code for the pin number corresponding to LED0 with pin number PF9 is as follows:
```c
#define LED0_PIN GET_PIN(F, 9)
```
#### View Driver Files
If you use a different BSP, you will need to check the PIN driver code `drv_gpio.c` file to confirm the pin number. There is an array in this file that holds the number information for each PIN pin, as shown below:
```c
static const rt_uint16_t pins[] =
{
__STM32_PIN_DEFAULT,
__STM32_PIN_DEFAULT,
__STM32_PIN(2, A, 15),
__STM32_PIN(3, B, 5),
__STM32_PIN(4, B, 8),
__STM32_PIN_DEFAULT,
__STM32_PIN_DEFAULT,
__STM32_PIN_DEFAULT,
__STM32_PIN(8, A, 14),
__STM32_PIN(9, B, 6),
... ...
}
```
Take `__STM32_PIN(2, A, 15)` as an example, 2 is the pin number used by RT-Thread, A is the port number, and 15 is the pin number, so the pin number corresponding to PA15 is 2.
### Set Pin Mode
Before the pin is used, you need to set the input or output mode first, and the following functions are used:
```c
void rt_pin_mode(rt_base_t pin, rt_base_t mode);
```
| Parameter | **Discription** |
| --------- | ------------------ |
| pin | Pin number |
| mode | Pin operation mode |
At present, the pin working mode supported by RT-Thread can take one of the five macro definition values as shown. The mode supported by the chip corresponding to each mode needs to refer to the specific implementation of the PIN device driver:
```c
#define PIN_MODE_OUTPUT 0x00 /* Output */
#define PIN_MODE_INPUT 0x01 /* Input */
#define PIN_MODE_INPUT_PULLUP 0x02 /* input Pull up */
#define PIN_MODE_INPUT_PULLDOWN 0x03 /* input Pull down */
#define PIN_MODE_OUTPUT_OD 0x04 /* output Open drain */
```
An example of use is as follows:
```c
#define BEEP_PIN_NUM 35 /* PB0 */
/* Buzzer pin is in output mode */
rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
```
### Set The Pin Level
The function to set the pin output level is as follows:
```c
void rt_pin_write(rt_base_t pin, rt_base_t value);
```
| **Parameter** | Discription |
|----------|-------------------------|
| pin | Pin number |
| value | Level logic value, which can take one of two macro definition values: PIN_LOW means low level, or PIN_HIGH means high level |
Examples of use are as follows:
```c
#define BEEP_PIN_NUM 35 /* PB0 */
/* Beep's pin is in output mode */
rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
/* Set low level */
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
```
### Read Pin Level
The functions to read the pin level are as follows:
```c
int rt_pin_read(rt_base_t pin);
```
| Parameter | Description |
| ---------- | ----------- |
| pin | Pin number |
| **return** | —— |
| PIN_LOW | Low level |
| PIN_HIGH | High level |
Examples of use are as follows:
```c
#define BEEP_PIN_NUM 35 /* PB0 */
int status;
/* Buzzer pin is in output mode */
rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
/* Set low level */
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
status = rt_pin_read(BEEP_PIN_NUM);
```
### Bind Pin Interrupt Callback Function
To use the interrupt function of the pin, you can use the following function to configure a pin to some interrupt trigger mode and bind an interrupt callback function to the corresponding pin. When the pin interrupt occurs, the callback function will be executed. :
```c
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
void (*hdr)(void *args), void *args);
```
| Parameter | Description |
| ---------- | ------------------------------------------------------------ |
| pin | Pin number |
| mode | Interrupt trigger mode |
| hdr | Interrupt callback function. Users need to define this function |
| args | Interrupt the parameters of the callback function, set to RT_NULL when not needed |
| return | —— |
| RT_EOK | Binding succeeded |
| error code | Binding failed |
Interrupt trigger mode mode can take one of the following five macro definition values:
```c
#define PIN_IRQ_MODE_RISING 0x00 /* Rising edge trigger */
#define PIN_IRQ_MODE_FALLING 0x01 /* Falling edge trigger */
#define PIN_IRQ_MODE_RISING_FALLING 0x02 /* Edge trigger (triggered on both rising and falling edges)*/
#define PIN_IRQ_MODE_HIGH_LEVEL 0x03 /* High level trigger */
#define PIN_IRQ_MODE_LOW_LEVEL 0x04 /* Low level trigger */
```
Examples of use are as follows:
```c
#define KEY0_PIN_NUM 55 /* PD8 */
/* Interrupt callback function */
void beep_on(void *args)
{
rt_kprintf("turn on beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
}
static void pin_beep_sample(void)
{
/* Button 0 pin is the input mode */
rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* Bind interrupt, rising edge mode, callback function named beep_on */
rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
}
```
### Enable Pin Interrupt
After binding the pin interrupt callback function, use the following function to enable pin interrupt:
```c
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);
```
| **Parameter** | **Description** |
|----------|----------------|
| pin | Pin number |
| enabled | Status, one of two values: PIN_IRQ_ENABLE, and PIN_IRQ_DISABLE |
| **return** | —— |
| RT_EOK | Enablement succeeded |
| error code | Enablement failed |
Examples of use are as follows:
```c
#define KEY0_PIN_NUM 55 /* PD8 */
/* Interrupt callback function */
void beep_on(void *args)
{
rt_kprintf("turn on beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
}
static void pin_beep_sample(void)
{
/* Key 0 pin is the input mode */
rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* Bind interrupt, rising edge mode, callback function named beep_on */
rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
/* Enable interrupt */
rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);
}
```
### Detach Pin Interrupt Callback Function
You can use the following function to detach the pin interrupt callback function:
```c
rt_err_t rt_pin_detach_irq(rt_int32_t pin);
```
| **Parameter** | **Description** |
| ------------- | -------------------- |
| pin | Pin number |
| **return** | —— |
| RT_EOK | Detachment succeeded |
| error code | Detachment failed |
After the pin detaches the interrupt callback function, the interrupt is not closed. You can also call the bind interrupt callback function to bind the other callback functions again.
```c
#define KEY0_PIN_NUM 55 /* PD8 */
/* Interrupt callback function */
void beep_on(void *args)
{
rt_kprintf("turn on beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
}
static void pin_beep_sample(void)
{
/* Key 0 pin is the input mode */
rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* Bind interrupt, rising edge mode, callback function named beep_on */
rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
/* Enable interrupt */
rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);
/* Detach interrupt callback function */
rt_pin_detach_irq(KEY0_PIN_NUM);
}
```
## PIN Device Usage Example
The following sample code is the pin device usage example. The main steps of the sample code are as follows:
1. Set the corresponding pin of the beep to the output mode and give a default low state.
2. Set the key 0 and button 1 corresponding to the input mode, then bind the interrupt callback function and enable the interrupt.
3. When the key 0 is pressed, the beep starts to sound, and when the key 1 is pressed, the beep stops.
```c
/*
* Program listing: This is a PIN device usage routine
* The routine exports the pin_beep_sample command to the control terminal
* Command call formatpin_beep_sample
* Program function: control the buzzer by controlling the level state of the corresponding pin of the buzzer by pressing the button
*/
#include <rtthread.h>
#include <rtdevice.h>
/* Pin number, determined by looking at the device driver file drv_gpio.c */
#ifndef BEEP_PIN_NUM
#define BEEP_PIN_NUM 35 /* PB0 */
#endif
#ifndef KEY0_PIN_NUM
#define KEY0_PIN_NUM 55 /* PD8 */
#endif
#ifndef KEY1_PIN_NUM
#define KEY1_PIN_NUM 56 /* PD9 */
#endif
void beep_on(void *args)
{
rt_kprintf("turn on beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
}
void beep_off(void *args)
{
rt_kprintf("turn off beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
}
static void pin_beep_sample(void)
{
/* Beep pin is in output mode */
rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
/* Default low level */
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
/* KEY 0 pin is the input mode */
rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* Bind interrupt, falling edge mode, callback function named beep_on */
rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
/* Enable interrupt */
rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);
/* KEY 1 pin is input mode */
rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* Binding interrupt, falling edge mode, callback function named beep_off */
rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL);
/* Enable interrupt */
rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);
}
/* Export to the msh command list */
MSH_CMD_EXPORT(pin_beep_sample, pin beep sample);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,265 @@
# PWM Device
## Introduction to PWM
PWM (Pulse Width Modulation) is a method of digitally encoding the level of an analog signal. The frequency of the square wave is used to encode the level of a specific analog signal by pulses of different frequencies. The output receives a series of pulses of equal magnitude and uses these pulses to replace the device with the desired waveform.
![PWM Schematic Diagram](figures/pwm-f.png)
Above is a simple schematic diagram of PWM. Assuming that the timer works in a up-counter mode. When the count value is less than the threshold, it outputs a level state, such as a high level. When the count value is greater than the threshold, it outputs the opposite, such as a low level. When the count value reaches the maximum value, the counter recounts from 0 and returns to the original level state. The ratio of the high-level duration (pulse width) to the cycle time is the duty cycle, ranging from 0 to 100%. The high level of the above picture is just half of the cycle time, so the duty cycle is 50%.
One of the common PWM control scenarios is to adjust the brightness of the light or screen. The brightness can be adjusted according to the duty cycle. The PWM adjusts the brightness not continuously, but constantly lights up and turns off the screen. When the light is turned on and off fast enough, the naked eye will always think that it is always bright. In the process of on and off, the longer the light is off, the lower the brightness of the screen to the naked eye. The longer the light is on, the less time is spent and the screen will be brighter.
![PWM Brightness Adjustment](figures/pwm-l.png)
## Access to PWM Devices
The application accesses the PWM device hardware through the PWM device management interface provided by RT-Thread. The related interfaces are as follows:
| **Function** | Description |
| ----------------- | ---------------------------------- |
| rt_device_find() | Find device handles based on the name of PWM device |
| rt_pwm_set() | Set PWM period and pulse width |
| rt_pwm_enable() | Enable PWM device |
| rt_pwm_disable() | Disable the PWM device |
### Find the PWM Device
The application obtains the device handle based on the name of PWM device, which in turn can operate the PWM device. The function is as follows:
```c
rt_device_t rt_device_find(const char* name);
```
| Parameter | Description |
| -------- | ---------------------------------- |
| name | Device |
| **Return** | —— |
| Device handle | Found the corresponding device, will return the corresponding device handle |
| RT_NULL | Device not found |
In general, the name of the PWM device registered to the system is pwm0, pwm1, etc. The usage examples are as follows:
```c
#define PWM_DEV_NAME "pwm3" /* name of PWM device */
struct rt_device_pwm *pwm_dev; /* PWM device handle */
/* Search the device */
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
```
### Set PWM Period and Pulse Width
Set the PWM period and duty cycle by using the following function:
```c
rt_err_t rt_pwm_set(struct rt_device_pwm *device,
int channel,
rt_uint32_t period,
rt_uint32_t pulse);
```
| Parameter | Description |
| ---------- | ----------------- |
| device | PWM device handle |
| channel | PWM channel |
| period | PWM period (ns) |
| pulse | PWM pulse width time (ns) |
| **Return** | —— |
| RT_EOK | successful |
| -RT_EIO | device is null |
| -RT_ENOSYS | Device operation method is null |
| Other Errors | Execute failed |
The output frequency of the PWM is determined by the period. For example, the time of a period is 0.5ms (milliseconds), the period value is 500000ns (nanoseconds), the output frequency is 2KHz, the duty cycle is `pulse / period`, and the pulse value cannot exceed period.
An example of use is as follows:
```c
#define PWM_DEV_NAME "pwm3" /* name of PWM device */
#define PWM_DEV_CHANNEL 4 /* PWM channel */
struct rt_device_pwm *pwm_dev; /* PWM device handle */
rt_uint32_t period, pulse;
period = 500000; /* The period is 0.5ms, the unit is nanoseconds */
pulse = 0; /* PWM pulse width value, the unit is nanoseconds */
/* Search the device */
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
/* Set the PWM period and pulse width */
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
```
### Enable the PWM Device
After setting the PWM period and pulse width, you can enable the PWM device by the following function:
```c
rt_err_t rt_pwm_enable(struct rt_device_pwm *device, int channel);
```
| Parameter | Description |
| ---------- | ------------------------------- |
| device | PWM device handle |
| channel | PWM channel |
| **Return** | —— |
| RT_EOK | Enable device successful |
| -RT_ENOSYS | Device operation method is null |
| Other Errors | Enable device failed |
An example of use is as follows:
```c
#define PWM_DEV_NAME "pwm3" /* name of PWM device */
#define PWM_DEV_CHANNEL 4 /* PWM channel */
struct rt_device_pwm *pwm_dev; /* PWM device handle */
rt_uint32_t period, pulse;
period = 500000; /* The period is 0.5ms, the unit is nanoseconds */
pulse = 0; /* PWM pulse width value, the unit is nanoseconds */
/* Search the device */
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
/* Set the PWM period and pulse width */
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
/* Enable the device */
rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL);
```
### Disable the PWM device Channel
Use the following function to turn off the corresponding channel of the PWM device.
```c
rt_err_t rt_pwm_disable(struct rt_device_pwm *device, int channel);
```
| **Parameter** | Description |
| ---------- | ------------------------------- |
| device | PWM device handle |
| channel | PWM channel |
| **Return** | —— |
| RT_EOK | Turn off device successful |
| -RT_EIO | Device handle is null |
| Other Errors | Turn off device failed |
An example of use is as follows:
```c
#define PWM_DEV_NAME "pwm3" /* name of PWM device */
#define PWM_DEV_CHANNEL 4 /* PWM channel */
struct rt_device_pwm *pwm_dev; /* PWM device handle */
rt_uint32_t period, pulse;
period = 500000; /* The period is 0.5ms, the unit is nanoseconds */
pulse = 0; /* PWM pulse width value, the unit is nanoseconds */
/* Search the device */
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
/* Set the PWM period and pulse width */
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
/* Enable the device */
rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL);
/* Turn off the device channel */
rt_pwm_disable(pwm_dev,PWM_DEV_CHANNEL);
```
## FinSH Command
To set the period and duty cycle of a channel of a PWM device, use the command `pwm_set pwm1 1 500000 5000`. The first parameter is the command, the second parameter is the PWM device name, the third parameter is the PWM channel, and the fourth parameter is PWM period(ns), the fifth parameter is the pulse width (ns).
```c
msh />pwm_set pwm1 1 500000 5000
msh />
```
To enable a channel of the PWM device, use the command`pwm_enable pwm1 1`. The first parameter is the command, the second parameter is the PWM device name, and the third parameter is the PWM channel.
```c
msh />pwm_enable pwm1 1
msh />
```
To disable a channel of the PWM device, use the command `pwm_disable pwm1 1`. The first parameter is the command, the second parameter is the PWM device name, and the third parameter is the PWM channel.
```c
msh />pwm_disable pwm1 1
msh />
```
## PWM Device Usage Example
The following sample code is a PWM device usage sample . The main steps of the sample code are as follows:
1. Find the PWM device to get the device handle.
2. Set the PWM period and pulse width.
3. Enable the PWM device.
4. The pulse width is modified every 50 milliseconds in the while loop.
5. Connect the PWM channel to a LED, and you can see that the LED changes from dark to bright gradually, and then from bright to dark.
```c
/*
* Program list: This is PWM device usage example
* The routine exports the pwm_led_sample command to the control terminal
* Format for Command: pwm_led_sample
* Program function: By controlling the brightness of the LED light through the PWM device,
* you can see that the LED changes from dark to bright gradually, then from bright to dark.
*/
#include <rtthread.h>
#include <rtdevice.h>
#define PWM_DEV_NAME "pwm3" /* PWM device name */
#define PWM_DEV_CHANNEL 4 /* PWM channel */
struct rt_device_pwm *pwm_dev; /* PWM device handle */
static int pwm_led_sample(int argc, char *argv[])
{
rt_uint32_t period, pulse, dir;
period = 500000; /* The period is 0.5ms, the unit is nanoseconds */
dir = 1; /* Increase or decrease direction of PWM pulse width value */
pulse = 0; /* PWM pulse width value, the unit is nanoseconds*/
/* Set LED pin mode to output */
rt_pin_mode(LED_PIN_NUM, PIN_MODE_OUTPUT);
/* Set high LED pin mode */
rt_pin_write(LED_PIN_NUM, PIN_HIGH);
/* Search the Device */
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
if (pwm_dev == RT_NULL)
{
rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME);
return RT_ERROR;
}
/* Set PWM period and pulse width defaults */
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
/* Enable device */
rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL);
while (1)
{
rt_thread_mdelay(50);
if (dir)
{
pulse += 5000; /* Increase 5000ns each time from 0 */
}
else
{
pulse -= 5000; /* 5000ns reduction from the maximum */
}
if (pulse >= period)
{
dir = 0;
}
if (0 == pulse)
{
dir = 1;
}
/* Set the PWM period and pulse width */
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
}
}
/* Export to the msh command list */
MSH_CMD_EXPORT(pwm_led_sample, pwm sample);
```

View File

@ -0,0 +1,198 @@
# RTC Device
## Introduction of RTC
The RTC (Real-Time Clock) provides accurate real-time clock time, which can be used to generate information such as year, month, day, hour, minute, and second. At present, most real-time clock chips use a higher precision crystal oscillator as a clock source. In order to work when the main power supply is powered down, some clock chips will be powered by a battery to keep the time information valid.
The RT-Thread RTC device provides the basic services for the operating system's time system. In the face of more and more IoT scenarios, RTC has become the standard configuration of the product, and even in the secure transmission process such as SSL, RTC has become an indispensable part.
## Access RTC Devices
The application accesses the RTC hardware through the RTC device management interface, and the relevant interfaces are as follows:
| **Function** | Description |
| ------------- | ---------------------------------- |
| set_date() | Set date, year, month, day |
| set_time() | Set time, hour, minute, second |
| time() | Obtain current time |
### Set Date
Set the current date value of the RTC device by the following functions:
```c
rt_err_t set_date(rt_uint32_t year, rt_uint32_t month, rt_uint32_t day)
```
| **Parameter** | **Description** |
| -------- | ---------------------------------- |
|year |The year to be set to take effect|
|month |The month to be set to take effect|
|day | The date to be set to take effect |
| **return** | —— |
| RT_EOK | Set-up succeeded |
| -RT_ERROR | Set-up failed, no rtc device found |
| other error code | Set-up failed |
An example of use is as follows:
```c
/* Set the date to December 3, 2018 */
set_date(2018, 12, 3);
```
### Set Time
Set the current time value of the RTC device by the following function:
```c
rt_err_t set_time(rt_uint32_t hour, rt_uint32_t minute, rt_uint32_t second)
```
| **Parameter** | **Description** |
| ---------- | ------------------------------- |
|hour |The hour to be set to take effect|
|minute |The minute to be set to take effect|
|second |The second to be set to take effect|
| **return** | —— |
| RT_EOK | Set-up succeeded |
| -RT_ERROR | Set-up failed, no rtc device found |
| other error code | Set-up failed |
An example of use is as follows:
```c
/* Set the time to 11:15:50 */
set_time(11, 15, 50);
```
### Obtain Current Time
Obtain time using the time API in the C standard library:
```c
time_t time(time_t *t)
```
| **Parameter** | **Description** |
| ---------- | ------------------------------- |
|t |Time data pointer |
| **return** | —— |
| Current time value | |
Examples of use are as follows:
```c
time_t now; /* Save the current time value obtained */
/* Obtain Time */
now = time(RT_NULL);
/* Printout time information */
rt_kprintf("%s\n", ctime(&now));
```
>Currently only one RTC device is allowed in the system and the name is `"rtc"`.
## Functional Configuration
### Enable Soft RTC (Software Emulation RTC)
You can use the function of enabling RTC software emulation, which is ideal for products that do not require high time precision and have no hardware RTC. The configuration options of menuconfig are as follows:
```c
RT-Thread Components →
Device Drivers:
-*- Using RTC device drivers /* Use RTC device driver */
[ ] Using software simulation RTC device /* Use software simulation RTC device */
```
### Enable NTP Time Automatic Synchronization
If the RT-Thread is connected to the Internet, you can enable automatic NTP time synchronization to synchronize local time periodically.
First open the NTP function in menuconfig as follows:
```c
RT-Thread online packages →
IoT - internet of things →
netutils: Networking utilities for RT-Thread:
[*] Enable NTP(Network Time Protocol) client
```
After the NTP is turned on, the RTC's automatic synchronization function will be automatically turned on, and the synchronization period and the delay time of the first synchronization can also be set:
```c
RT-Thread Components →
Device Drivers:
-*- Using RTC device drivers /* Use RTC device driver */
[ ] Using software simulation RTC device /* Use software simulation RTC device */
[*] Using NTP auto sync RTC time /* Automatically synchronize RTC time with NTP */
(30) NTP first sync delay time(second) for network connect /* The delay for performing NTP time synchronization for the first time. The purpose of the delay is to reserve a certain amount of time for the network connection and try to increase the success rate of the first NTP time synchronization. The default time is 30S */
(3600) NTP auto sync period(second) /* NTP The synchronization period is automatically synchronized in seconds, and the default period is one hour (ie 3600S). */
```
## FinSH Command
Enter `date` to view the current time.
```c
msh />date
Fri Feb 16 01:11:56 2018
msh />
```
Also use the `date` command, after the command, enter `year` `month` `date` `hour ` ` minute ` ` second ` (between spaces, 24H system), and set the current time to 2018-02-16 01:15:30. The approximate effect is as follows:
```c
msh />date 2018 02 16 01 15 30
msh />
```
## RTC Device Usage Examples
For the specific usage of the RTC device, refer to the following example code. First, set the year, month, date, hour, minute and second information, and then delay the data for 3 seconds to get the current time information.
```c
/*
* Program listing: This is an RTC device usage routine
* The routine exports the rtc_sample command to the control terminal
* Command call formatrtc_sample
* Program function: Set the date and time of the RTC device. After a delay, obtain the current time and print the display.
*/
#include <rtthread.h>
#include <rtdevice.h>
static int rtc_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
time_t now;
/* Set date */
ret = set_date(2018, 12, 3);
if (ret != RT_EOK)
{
rt_kprintf("set RTC date failed\n");
return ret;
}
/* Set time */
ret = set_time(11, 15, 50);
if (ret != RT_EOK)
{
rt_kprintf("set RTC time failed\n");
return ret;
}
/* Delay 3 seconds */
rt_thread_mdelay(3000);
/* Obtain Time */
now = time(RT_NULL);
rt_kprintf("%s\n", ctime(&now));
return ret;
}
/* Export to the msh command list */
MSH_CMD_EXPORT(rtc_sample, rtc sample);
```

View File

@ -0,0 +1,465 @@
# Sensor Device
## Introduction
Sensor is an important part of the Internet of Things, and "Sensor to the Internet of Things" is equivalent to "eyes to humans". Without eyes, human beings can not see the vast world of flowers. The same is true for the Internet of Things.
Nowadays, with the development of Internet of Things, a large number of Sensors have been developed for developers to choose, such as Accelerometer, Magnetometer, Gyroscope, Barometer/pressure, Humidometer and so on. These sensors, manufactured by the world's leading semiconductor manufacturers, have increased market selectivity and made application development more difficult. Because different sensor manufacturers and sensors need their own unique drivers to run, so when developing applications, they need to adapt to different sensors, which naturally increases the difficulty of development. In order to reduce the difficulty of application development and increase the reusability of sensor driver, we designed a Sensor device.
The function of Sensor device is to provide a unified operation interface for the upper layer and improve the reusability of the upper code.
### Characteristics of Sensor Device
- **Interface**: Standard device interface (open/close/read/control)
- **Work mode**: support polling, interruption, FIFO three modes
- **Power mode**: support four modes: power failure, common, low power consumption and high power consumption
## Access Sensor Device
The application accesses the sensor device through the I/O device management interface provided by RT-Thread. The related interfaces are as follows:
| Functions | Description |
| --------------------------- | ------------------------------------------------------------ |
| rt_device_find() | Finding device handles based on device name of sensor device |
| rt_device_open() | open sensor device |
| rt_device_read() | read data |
| rt_device_control() | control sensor device |
| rt_device_set_rx_indicate() | setting reveive callback fuction |
| rt_device_close() | close sensor device |
### Find Sensor Device
The application obtains the device handle according to the name of the sensor device, and then can operate the sensor device. The function of finding the device is as follows:
```c
rt_device_t rt_device_find(const char* name);
```
| **Parameter** | **Description** |
| ------------- | ------------------------------------------------------------ |
| name | sensor device name |
| **return** | —— |
| handle | Finding the corresponding device returns the corresponding device handle |
| RT_NULL | No corresponding device object was found |
The use example is as follows:
```c
#define SENSOR_DEVICE_NAME "acce_st" /* sensor device name */
static rt_device_t sensor_dev; /* sensor device handle */
/* Find the sensor device according to the device name and get the device handle */
sensor_dev = rt_device_find(SENSOR_DEVICE_NAME);
```
### Open Sensor Device
Through the device handle, the application can open and close the device. When the device is opened, it will check whether the device has been initialized or not. If it is not initialized, it will call the initialization interface initialization device by default. Open the device through the following functions:
```c
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
```
| **Parameter** | **Description** |
| ------------- | ------------------------------------------------------------ |
| dev | device handle |
| oflags | open mode flag |
| **Return** | —— |
| RT_EOK | open success |
| -RT_EBUSY | If the RT_DEVICE_FLAG_STANDALONE parameter is included in the parameter specified at the time of device registration, the device will not be allowed to open repeatedly. |
| -RT_EINVAL | Unsupported open mode |
| other err | open failed |
The oflags parameter supports the following parameters
```c
#define RT_DEVICE_FLAG_RDONLY 0x001 /* Read-only mode for standard device, polling mode for corresponding sensors */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* Interrupt Receiving Mode */
#define RT_DEVICE_FLAG_FIFO_RX 0x200 /* FIFO receiving mode */
```
There are three modes of receiving and sending sensor data: interrupt mode, polling mode and FIFO mode. When using these three modes, **only one of them can be chosen**. If the sensor's open parameter oflags does not specify the use of interrupt mode or FIFO mode, polling mode is used by default.
FIFO ,means first Input first output. FIFO transmission mode needs sensor hardware support, data is stored in hardware FIFO, read multiple data at a time, which saves CPU resources to do other operations. Very useful in low power mode
If the sensor uses FIFO receiving mode, the value of oflags is RT_DEVICE_FLAG_FIFO_RX.
An example of turning on sensor devices in polling mode is as follows:
```c
#define SAMPLE_SENSOR_NAME "acce_st" /* sensor device name */
int main(void)
{
rt_device_t dev;
struct rt_sensor_data data;
/* find sensor device */
dev = rt_device_find(SAMPLE_SENSOR_NAME);
/* Open sensor devices in read-only and polling mode */
rt_device_open(dev, RT_DEVICE_FLAG_RDWR);
if (rt_device_read(dev, 0, &data, 1) == 1)
{
rt_kprintf("acce: x:%5d, y:%5d, z:%5d, timestamp:%5d\n", data.data.acce.x, data.data.acce.y, data.data.acce.z, data.timestamp);
}
rt_device_close(dev);
return RT_EOK;
}
```
### Control Sensor Device
By command control word, the application program can configure the sensor device through the following functions:
```c
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
```
| **Parameter** | **Description** |
| ------------- | ------------------------------------------------------------ |
| dev | device handle |
| cmd | command control word, see below for more details. |
| arg | the parameters of command control word, see below for more details. |
| **Return** | —— |
| RT_EOK | success |
| -RT_ENOSYS | faileddevice is NULL |
| other err | failed |
`cmd` currently supports the following command control words:
```c
#define RT_SEN_CTRL_GET_ID (0) /* read device ID */
#define RT_SEN_CTRL_GET_INFO (1) /* get device information */
#define RT_SEN_CTRL_SET_RANGE (2) /* Setting the measuring range of the sensor */
#define RT_SEN_CTRL_SET_ODR (3) /* Setting the Output Rate of Sensor Dataunit is HZ */
#define RT_SEN_CTRL_SET_MODE (4) /* Setting up working mode */
#define RT_SEN_CTRL_SET_POWER (5) /* Setting up power mode */
#define RT_SEN_CTRL_SELF_TEST (6) /* selfcheck */
```
#### Get device information
```c
struct rt_sensor_info info;
rt_device_control(dev, RT_SEN_CTRL_GET_INFO, &info);
LOG_I("vendor :%d", info.vendor);
LOG_I("model :%s", info.model);
LOG_I("unit :%d", info.unit);
LOG_I("intf_type :%d", info.intf_type);
LOG_I("period_min:%d", info.period_min);
```
#### Read Device ID
```c
rt_uint8_t reg = 0xFF;
rt_device_control(dev, RT_SEN_CTRL_GET_ID, &reg);
LOG_I("device id: 0x%x!", reg);
```
#### Setting the measuring range of the sensor
The unit that sets the measuring range of the sensor is the unit that is provided when the device is registered.
```c
rt_device_control(dev, RT_SEN_CTRL_SET_RANGE, (void *)1000);
```
#### Setting the Output Rate of Sensor Data
Set the output rate to 100HZ and call the following interface.
```c
rt_device_control(dev, RT_SEN_CTRL_SET_ODR, (void *)100);
```
#### Setting up working mode
```c
/* Set the working mode to polling mode */
rt_device_control(dev, RT_SEN_CTRL_SET_MODE, (void *)RT_SEN_MODE_POLLING);
/* Set working mode to interrupt mode */
rt_device_control(dev, RT_SEN_CTRL_SET_MODE, (void *)RT_SEN_MODE_INT);
/* Set working mode to FIFO mode */
rt_device_control(dev, RT_SEN_CTRL_SET_MODE, (void *)RT_SEN_MODE_FIFO);
```
#### Setting up power mode
```c
/* Set power mode to power-off mode */
rt_device_control(dev, RT_SEN_CTRL_SET_POWER, (void *)RT_SEN_POWER_DOWN);
/* Set power mode to normal mode */
rt_device_control(dev, RT_SEN_CTRL_SET_POWER, (void *)RT_SEN_POWER_NORMAL);
/* Setting Power Mode to Low Power Consumption Mode */
rt_device_control(dev, RT_SEN_CTRL_SET_POWER, (void *)RT_SEN_POWER_LOW);
/* Setting Power Mode to High Performance Mode */
rt_device_control(dev, RT_SEN_CTRL_SET_POWER, (void *)RT_SEN_POWER_HIGH);
```
#### Device self-inspection
```c
int test_res;
/* Control equipment self-check and return the results. Returning RT_EOK indicates success of self-check and other values indicate failure of self-check. */
rt_device_control(dev, RT_SEN_CTRL_SELF_TEST, &test_res);
```
### Setting Reveive Callback Fuction
Data reception instructions can be set by following functions. When the sensor receives data, it notifies the upper application thread that data arrives:
```c
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
```
| **Parameter** | **Description** |
| ------------- | --------------------------------------------- |
| dev | device handle |
| rx_ind | Callback function pointer |
| dev | device handle(parameter of callback function) |
| size | buffer size(parameter of callback function) |
| **Return** | —— |
| RT_EOK | Successful setup |
The callback function of the function is provided by the user. If the sensor is opened in interrupt mode, when the sensor receives data and interrupts, the callback function will be called, and the data size of the buffer will be placed in the `size` parameter, and the sensor device handle will be placed in the `dev` parameter for users to obtain.
Generally, receiving callback function can send a semaphore or event to inform sensor data processing thread that data arrives. The use example is as follows:
```c
#define SAMPLE_SENSOR_NAME "acce_st" /* sensor device name */
static rt_device_t dev; /* sensoe device handle*/
static struct rt_semaphore rx_sem; /* The semaphore used to receive messages */
/* Callback function for receiving data */
static rt_err_t sensor_input(rt_device_t dev, rt_size_t size)
{
/* When the sensor receives the data, it generates an interrupt, calls the callback function, and sends the semphore . */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static int sensor_sample(int argc, char *argv[])
{
dev = rt_device_find(SAMPLE_SENSOR_NAME);
/* Open Sensor Device in Interrupt Receive and Poll Send Mode */
rt_device_open(dev, RT_DEVICE_FLAG_INT_RX);
/* init semphore */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* setting reveive callback function */
rt_device_set_rx_indicate(dev, sensor_input);
}
```
### Read Data of Sensor Device
The following functions can be called to read the data received by the sensor:
```c
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
```
| **Parameter** | **Description** |
| ---------------------- | ------------------------------------------------------------ |
| dev | device handle |
| pos | Read data offset, sensor does not use this parameter |
| buffer | Buffer pointer, read data will be saved in the buffer |
| size | Size of read data |
| **Return** | —— |
| Real size of read data | Returns the number of read data |
| 0 | The errno of the current thread needs to be read to determine the error status |
The sensor uses the interrupt receiving mode and cooperates with the receiving callback function as follows:
```c
static rt_device_t dev; /* sensor device handle */
static struct rt_semaphore rx_sem; /* The semaphore used to receive messages */
/* Threads receiving data */
static void sensor_irq_rx_entry(void *parameter)
{
rt_device_t dev = parameter;
struct rt_sensor_data data;
rt_size_t res;
while (1)
{
rt_sem_take(rx_sem, RT_WAITING_FOREVER);
res = rt_device_read(dev, 0, &data, 1);
if (res == 1)
{
sensor_show_data(dev, &data);
}
}
}
```
The sensor uses FIFO receiving mode and cooperates with receiving callback function as follows:
```c
static rt_sem_t sensor_rx_sem = RT_NULL;
rt_err_t rx_cb(rt_device_t dev, rt_size_t size)
{
rt_sem_release(sensor_rx_sem);
return 0;
}
static void sensor_fifo_rx_entry(void *parameter)
{
rt_device_t dev = parameter;
struct rt_sensor_data data;
rt_size_t res, i;
data = rt_malloc(sizeof(struct rt_sensor_data) * 32);
while (1)
{
rt_sem_take(sensor_rx_sem, RT_WAITING_FOREVER);
res = rt_device_read(dev, 0, data, 32);
for (i = 0; i < res; i++)
{
sensor_show_data(dev, &data[i]);
}
}
}
int main(void)
{
static rt_thread_t tid1 = RT_NULL;
rt_device_t dev;
struct rt_sensor_data data;
sensor_rx_sem = rt_sem_create("sen_rx_sem", 0, RT_IPC_FLAG_FIFO);
tid1 = rt_thread_create("sen_rx_thread",
sensor_fifo_rx_entry, dev,
1024,
15, 5);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
dev = rt_device_find("acce_st");
rt_device_set_rx_indicate(dev, rx_cb);
rt_device_open(dev, RT_SEN_FLAG_FIFO);
return RT_EOK;
}
```
### Close Sensor Device
When the application completes the sensor operation, the sensor device can be closed by the following functions:
```c
rt_err_t rt_device_close(rt_device_t dev);
```
| **Parameter** | **Description** |
| ------------- | ------------------------------------------------------------ |
| dev | device handle |
| **Return** | —— |
| RT_EOK | The equipment was closed successfully. |
| -RT_ERROR | The device has been completely shut down and cannot be closed repeatedly. |
| other err | failed to close th device |
Closing the device interface and opening the device interface should be used in pairs, opening the primary device should close the primary device, so that the device will be completely closed, otherwise the device is still in an open state.
## Example Code for Sensor Device
The specific use of sensor devices can be referred to the following sample code, the main steps of the sample code are as follows:
1. Find the sensor device first and get the device handle.
2. Open the sensor device by polling.
3. Read the data five times in a row and print it out.
4. Close the sensor device.
This sample code is not limited to a specific BSP. According to the BSP registered sensor device, input different dev_name to run.
```c
/*
* Program List: This is a routine for sensor devices
* The routine exports the sensor_sample command to the control terminal
* Command Call Formatsensor_sample dev_name
* Command Interpretation: The second parameter of the command is the name of the sensor device to be used.
* Program function: Open the corresponding sensor, and then read the data five times in a row and print it out.
*/
#include "sensor.h"
static void sensor_show_data(rt_size_t num, rt_sensor_t sensor, struct rt_sensor_data *sensor_data)
{
switch (sensor->info.type)
{
case RT_SENSOR_CLASS_ACCE:
rt_kprintf("num:%3d, x:%5d, y:%5d, z:%5d, timestamp:%5d\n", num, sensor_data->data.acce.x, sensor_data->data.acce.y, sensor_data->data.acce.z, sensor_data->timestamp);
break;
case RT_SENSOR_CLASS_GYRO:
rt_kprintf("num:%3d, x:%8d, y:%8d, z:%8d, timestamp:%5d\n", num, sensor_data->data.gyro.x, sensor_data->data.gyro.y, sensor_data->data.gyro.z, sensor_data->timestamp);
break;
case RT_SENSOR_CLASS_MAG:
rt_kprintf("num:%3d, x:%5d, y:%5d, z:%5d, timestamp:%5d\n", num, sensor_data->data.mag.x, sensor_data->data.mag.y, sensor_data->data.mag.z, sensor_data->timestamp);
break;
case RT_SENSOR_CLASS_HUMI:
rt_kprintf("num:%3d, humi:%3d.%d%%, timestamp:%5d\n", num, sensor_data->data.humi / 10, sensor_data->data.humi % 10, sensor_data->timestamp);
break;
case RT_SENSOR_CLASS_TEMP:
rt_kprintf("num:%3d, temp:%3d.%dC, timestamp:%5d\n", num, sensor_data->data.temp / 10, sensor_data->data.temp % 10, sensor_data->timestamp);
break;
case RT_SENSOR_CLASS_BARO:
rt_kprintf("num:%3d, press:%5d, timestamp:%5d\n", num, sensor_data->data.baro, sensor_data->timestamp);
break;
case RT_SENSOR_CLASS_STEP:
rt_kprintf("num:%3d, step:%5d, timestamp:%5d\n", num, sensor_data->data.step, sensor_data->timestamp);
break;
default:
break;
}
}
static void sensor_sample(int argc, char **argv)
{
rt_device_t dev = RT_NULL;
struct rt_sensor_data data;
rt_size_t res, i;
/* Finding Sensor Devices in the System */
dev = rt_device_find(argv[1]);
if (dev == RT_NULL)
{
rt_kprintf("Can't find device:%s\n", argv[1]);
return;
}
/* Open sensor devices in polling mode */
if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK)
{
rt_kprintf("open device failed!");
return;
}
for (i = 0; i < 5; i++)
{
/* Read a data from a sensor */
res = rt_device_read(dev, 0, &data, 1);
if (res != 1)
{
rt_kprintf("read data failed!size is %d", res);
}
else
{
sensor_show_data(i, (rt_sensor_t)dev, &data);
}
rt_thread_mdelay(100);
}
/* Close the sensor device */
rt_device_close(dev);
}
MSH_CMD_EXPORT(sensor_sample, sensor device sample);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,741 @@
# SPI Device
## Introduction to SPI
SPI (Serial Peripheral Interface) is a high-speed, full-duplex, synchronous communication bus commonly used for short-range communication. It is mainly used in EEPROM, FLASH, real-time clock, AD converter, and digital signal processing and between the device and the digital signal decoder. SPI generally uses 4 lines of communication, as shown in the following figure:
![Ways of communication from SPI Master to SPI Slave](figures/spi1.png)
* MOSI :SPI Bus Master Output/Slave Input.
* MISO :SPI Bus Master Input/Slave Output.
* SCLK :Serial Clock, Master device outputs clock signal to slave device.
* CS : select the slave device, also called SS, CSB, CSN, EN, etc., the master device outputs a chip select signal to the slave device.
The SPI works in master-slave mode and usually has one master and one or more slaves. The communication is initiated by the master device. The master device selects the slave device to communicate through CS, and then provides a clock signal to the slave device through SCLK. The data is output to the slave device through the MOSI, and the data sent by the slave device is received through the MISO.
As shown in the figure below, the chip has two SPI controllers. The SPI controller corresponds to the SPI master. Each SPI controller can connect multiple SPI slaves. The slave devices mounted on the same SPI controller share three signal pins: SCK, MISO, MOSI, but the CS pins of each slave device are independent.
![Connect from one SPI controller to multiple SPI slaves](figures/spi2.png)
The master device selects the slave device by controlling the CS pin, typically active low. Only one CS pin is active on an SPI master, and the slave connected to the active CS pin can now communicate with the master.
The slave's clock is provided by the master through SCLK, and MOSI and MISO complete the data transfer based on SCLK. The working timing mode of the SPI is determined by the phase relationship between CPOL (Clock Polarity) and CPHA (Clock Phase). CPOL represents the state of the initial level of the clock signal. A value of 0 indicates that the initial state of the clock signal is low, and a value of 1 indicates that the initial level of the clock signal is high. CPHA indicates on which clock edge the data is sampled. A value of 0 indicates that the data is sampled on the first clock change edge, and a value of 1 indicates that the data is sampled on the second clock change edge. There are 4 working timing modes according to different combinations of CPOL and CPHA: ①CPOL=0, CPHA=0; ②CPOL=0, CPHA=1; ③CPOL=1, CPHA=0; ④CPOL=1, CPHA=1. As shown below:
![4 working timing modes of SPI](figures/spi5.png)
**QSPI:** QSPI is short for Queued SPI and is an extension of the SPI interface from Motorola, which is more extensive than SPI applications. Based on the SPI protocol, Motorola has enhanced its functionality, added a queue transfer mechanism, and introduced a queue serial peripheral interface protocol (QSPI protocol). Using this interface, users can transfer transmission queues containing up to 16 8-bit or 16-bit data at one time. Once the transfer is initiated, CPU is not required until the end of the transfer, greatly improving the transfer efficiency. Compared to SPI, the biggest structural feature of QSPI is the replacement of the transmit and receive data registers of the SPI with 80 bytes of RAM.
**Dual SPI Flash:** For SPI Flash, full-duplex is not commonly used. You can send a command byte into Dual mode and let it work in half-duplex mode to double data transfer. Thus, MOSI becomes SIO0 (serial io 0), and MISO becomes SIO1 (serial io 1), so that 2 bit data can be transmitted in one clock cycle, which doubles the data transmission.
**Quad SPI Flash:** Similar to the Dual SPI, Quad SPI Flash adds two I/O lines (SIO2, SIO3) to transfer 4 bits of data in one clock.
So for SPI Flash, there are three types of standard SPI Flash, Dual SPI Flash, Quad SPI Flash. At the same clock, the higher the number of lines, the higher the transmission rate.
## Mount SPI Device
The SPI driver registers the SPI bus and the SPI device needs to be mounted to the SPI bus that has already been registered.
```C
rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
const char *name,
const char *bus_name,
void *user_data)
```
| **Parameter** | Description |
| -------- | ---------------------------------- |
| device | SPI device handle |
| name | SPI device name |
| bus_name | SPI bus name |
| user_data | user data pointer |
| **Return** | —— |
| RT_EOK | Success |
| Other Errors | Failure |
This function is used to mount an SPI device to the specified SPI bus, register the SPI device with the kernel, and save user_data to the control block of the SPI device.
The general SPI bus naming principle is spix, and the SPI device naming principle is spixy. For example, spi10 means device 0 mounted on the spi1 bus. User_data is generally the CS pin pointer of the SPI device. When data is transferred, the SPI controller will operate this pin for chip select.
If you use the BSP in the `rt-thread/bsp/stm32` directory, you can use the following function to mount the SPI device to the bus:
```c
rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_TypeDef* cs_gpiox, uint16_t cs_gpio_pin);
```
The following sample code mounts the SPI FLASH W25Q128 to the SPI bus:
```c
static int rt_hw_spi_flash_init(void)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
rt_hw_spi_device_attach("spi1", "spi10", GPIOB, GPIO_PIN_14);
if (RT_NULL == rt_sfud_flash_probe("W25Q128", "spi10"))
{
return -RT_ERROR;
};
return RT_EOK;
}
/* Export to automatic initialization */
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);
```
## Configuring SPI Device
The SPI device's transmission parameters need to be configured after the SPI device is mounted to the SPI bus.
```c
rt_err_t rt_spi_configure(struct rt_spi_device *device,
struct rt_spi_configuration *cfg)
```
| **Parameter** | **Description** |
| -------- | ---------------------------------- |
| device | SPI device handle |
| cfg | SPI configuration parameter pointer |
| **Return** | —— |
| RT_EOK | Success |
This function saves the configuration parameters pointed to by `cfg` to the control block of the SPI device device, which is used when transferring data.
The `struct rt_spi_configuration` prototype is as follows:
```c
struct rt_spi_configuration
{
rt_uint8_t mode; /* mode */
rt_uint8_t data_width; /* data width, 8 bits, 16 bits, 32 bits */
rt_uint16_t reserved; /* reserved */
rt_uint32_t max_hz; /* maximum frequency */
};
```
**Mode: **Contains MSB/LSB, master-slave mode, timing mode, etc. The available macro combinations are as follows:
```c
/* Set the data transmission order whether the MSB bit is first or the LSB bit is before */
#define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */
#define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB */
/* Set the master-slave mode of the SPI */
#define RT_SPI_MASTER (0<<3) /* SPI master device */
#define RT_SPI_SLAVE (1<<3) /* SPI slave device */
/* Set clock polarity and clock phase */
#define RT_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */
#define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL = 0, CPHA = 1 */
#define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */
#define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL = 1, CPHA = 1 */
#define RT_SPI_CS_HIGH (1<<4) /* Chipselect active high */
#define RT_SPI_NO_CS (1<<5) /* No chipselect */
#define RT_SPI_3WIRE (1<<6) /* SI/SO pin shared */
#define RT_SPI_READY (1<<7) /* Slave pulls low to pause */
```
**Data width:** The data width format that can be sent and received by the SPI master and SPI slaves is set to 8-bit, 16-bit or 32-bit.
**Maximum Frequency** Set the baud rate for data transfer, also based on the baud rate range at which the SPI master and SPI slaves operate.
The example for configuration is as follows:
```c
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
cfg.max_hz = 20 * 1000 *1000; /* 20M */
rt_spi_configure(spi_dev, &cfg);
```
## QSPI Configuration
To configure the transmission parameters of a QSPI device, use the following function:
```c
rt_err_t rt_qspi_configure(struct rt_qspi_device *device, struct rt_qspi_configuration *cfg);
```
| **Parameter** | **Description** |
| -------- | ---------------------------------- |
| device | QSPI device handle |
| cfg | QSPI configuration parameter pointer |
| **Return** | —— |
| RT_EOK | Success |
This function saves the configuration parameters pointed to by `cfg` to the control block of the QSPI device, which is used when transferring data.
The `struct rt_qspi_configuration` prototype is as follows:
```c
struct rt_qspi_configuration
{
struct rt_spi_configuration parent; /* SPI device configuration parent */
rt_uint32_t medium_size; /* medium size */
rt_uint8_t ddr_mode; /* double rate mode */
rt_uint8_t qspi_dl_width ; /* QSPI bus width, single line mode 1 bit, 2 line mode 2 bits, 4 line mode 4 bits */
};
```
## Access SPI Device
In general, the MCU's SPI device communicates as a master and slave. In the RT-Thread, the SPI master is virtualized as an SPI bus device. The application uses the SPI device management interface to access the SPI slave device. The main interfaces are as follows:
| **Function** | **Description** |
| -------------------- | ---------------------------------- |
| rt_device_find() | Find device handles based on SPI device name |
| rt_spi_transfer_message() | Custom transfer data |
| rt_spi_transfer() | Transfer data once |
| rt_spi_send() | Send data once |
| rt_spi_recv() | Receive data one |
| rt_spi_send_then_send() | Send data twice |
| rt_spi_send_then_recv() | Send then Receive |
>The SPI data transfer related interface will call rt_mutex_take(). This function cannot be called in the interrupt service routine, which will cause the assertion to report an error.
### Find SPI Device
Before using the SPI device, you need to find and obtain the device handle according to the SPI device name, so that you can operate the SPI device. The device function is as follows.
```c
rt_device_t rt_device_find(const char* name);
```
| **Parameter** | **Description** |
| ------------- | ------------------------------------------------------------ |
| name | Device name |
| **Return** | —— |
| device handle | Finding the corresponding device will return the corresponding device handle |
| RT_NULL | Corresponding device object unfound |
In general, the name of the SPI device registered to the system is spi10, qspi10, etc. The usage examples are as follows:
```c
#define W25Q_SPI_DEVICE_NAME "qspi10" /* SPI device name */
struct rt_spi_device *spi_dev_w25q; /* SPI device handle */
/* Find the spi device to get the device handle */
spi_dev_w25q = (struct rt_spi_device *)rt_device_find(W25Q_SPI_DEVICE_NAME);
```
### Transfer Custom Data
By obtaining the SPI device handle, the SPI device management interface can be used to access the SPI device device for data transmission and reception. You can transfer messages by the following function:
```c
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device,struct rt_spi_message *message)
```
| **Parameter** | **Description** |
| ---------------- | ------------------------------------------------------------ |
| device | SPI device handle |
| message | message pointer |
| **Return** | —— |
| RT_NULL | Send successful |
| Non-null pointer | Send failed, return a pointer to the remaining unsent message |
This function can transmit a series of messages, the user can customize the value of each parameter of the message structure to be transmitted, so that the data transmission mode can be conveniently controlled. The `struct rt_spi_message` prototype is as follows:
```c
struct rt_spi_message
{
const void *send_buf; /* Send buffer pointer */
void *recv_buf; /* Receive buffer pointer */
rt_size_t length; /* Send/receive data bytes */
struct rt_spi_message *next; /* Pointer to the next message to continue sending */
unsigned cs_take : 1; /* Take chip selection*/
unsigned cs_release : 1; /* Release chip selection */
};
```
send_buf :sendbuf is the send buffer pointer. When the value is RT_NULL, it means that the current transmission is only receiving state, and no data needs to be sent.
recv_buf :recvbuf is the receive buffer pointer. When the value is RT_NULL, it means that the current transmission is in the transmit-only state. It does not need to save the received data, so the received data is directly discarded.
length :The unit of length is word, that is, when the data length is 8 bits, each length occupies 1 byte; when the data length is 16 bits, each length occupies 2 bytes.
next :The parameter next is a pointer to the next message to continue to send. If only one message is sent, the value of this pointer is RT_NULL. Multiple messages to be transmitted are connected together in a singly linked list by the next pointer.
cs_take :A cs_take value of 1 means that the corresponding CS is set to a valid state before data is transferred.
cs_release :A cs_release value of 1 indicates that the corresponding CS is released after the data transfer ends.
>When send_buf or recv_buf is not empty, the available size for both cannot be less than length.
If you use this function to transfer messages, the first message sent by cs_take needs to be set to 1. Set the chip to be valid, and the cs_release of the last message needs to be set to 1. Release the chip select.
An example of use is as follows:
```c
#define W25Q_SPI_DEVICE_NAME "qspi10" /* SPI device name */
struct rt_spi_device *spi_dev_w25q; /* SPI device handle */
struct rt_spi_message msg1, msg2;
rt_uint8_t w25x_read_id = 0x90; /* command */
rt_uint8_t id[5] = {0};
/* Find the spi device to get the device handle */
spi_dev_w25q = (struct rt_spi_device *)rt_device_find(W25Q_SPI_DEVICE_NAME);
/* Send command to read ID */
struct rt_spi_message msg1, msg2;
msg1.send_buf = &w25x_read_id;
msg1.recv_buf = RT_NULL;
msg1.length = 1;
msg1.cs_take = 1;
msg1.cs_release = 0;
msg1.next = &msg2;
msg2.send_buf = RT_NULL;
msg2.recv_buf = id;
msg2.length = 5;
msg2.cs_take = 0;
msg2.cs_release = 1;
msg2.next = RT_NULL;
rt_spi_transfer_message(spi_dev_w25q, &msg1);
rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[3], id[4]);
```
### Transfer Data Once
If only transfer data for once, use the following function:
```c
rt_size_t rt_spi_transfer(struct rt_spi_device *device,
const void *send_buf,
void *recv_buf,
rt_size_t length);
```
| **Parameter** | **Description** |
|----------|----------------------|
| device | SPI device handle |
| send_buf | Send data buffer pointer |
| recv_buf | Receive data buffer pointer |
| length | Length of data send/received |
| **Return** | —— |
| 0 | Transmission failed |
| Non-0 Value | Length of data successfully transferred |
This function is equivalent to calling `rt_spi_transfer_message()` to transfer a message. When starting to send data, the chip is selected. When the function returns, the chip is released. The message parameter is configured as follows:
```c
struct rt_spi_message msg
msg.send_buf = send_buf;
msg.recv_buf = recv_buf;
msg.length = length;
msg.cs_take = 1;
msg.cs_release = 1;
msg.next = RT_NULL;
```
### Send Data Once
If only send data once and ignore the received data, use the following function:
```c
rt_size_t rt_spi_send(struct rt_spi_device *device,
const void *send_buf,
rt_size_t length)
```
| **Parameter** | **Description** |
|----------|--------------------|
| device | SPI device handle |
| send_buf | Send data buffer pointer |
| length | Length of data sent |
| **Return** | —— |
| 0 | Transmission failed |
| Non-0 Value | Length of data successfully transferred |
Call this function to send the data of the buffer pointed to by send_buf, ignoring the received data. This function is a wrapper of the `rt_spi_transfer()` function.
This function is equivalent to calling `rt_spi_transfer_message()` to transfer a message. When the data starts to be sent, the chip is selected. When the function returns, the chip is released. The message parameter is configured as follows:
```c
struct rt_spi_message msg
msg.send_buf = send_buf;
msg.recv_buf = RT_NULL;
msg.length = length;
msg.cs_take = 1;
msg.cs_release = 1;
msg.next = RT_NULL;
```
### Receive Data Once
If only receive data once, use the following function:
```c
rt_size_t rt_spi_recv(struct rt_spi_device *device,
void *recv_buf,
rt_size_t length);
```
| **Parameter** | **Description** |
|----------|--------------------|
| device | SPI device handle |
| recv_buf | Send data buffer pointer |
| length | Length of data sent |
| **Return** | —— |
| 0 | Transmission failed |
| Non-0 Value | Length of data successfully transferred |
Call this function to receive the data and save it to the buffer pointed to by recv_buf. This function is a wrapper of the `rt_spi_transfer()` function. The SPI bus protocol stipulates that the master can only generate a clock, so when receiving data, the master will send the data 0XFF.
This function is equivalent to calling `rt_spi_transfer_message()` to transfer a message. When receiving data, the chip is selected. When the function returns, the chip is released. The message parameter is configured as follows:
```c
struct rt_spi_message msg
msg.send_buf = RT_NULL;
msg.recv_buf = recv_buf;
msg.length = length;
msg.cs_take = 1;
msg.cs_release = 1;
msg.next = RT_NULL;
```
### Send Data Twice in Succession
If need to send data of 2 buffers in succession and the CS is not released within the process, you can call the following function:
```c
rt_err_t rt_spi_send_then_send(struct rt_spi_device *device,
const void *send_buf1,
rt_size_t send_length1,
const void *send_buf2,
rt_size_t send_length2);
```
| **Parameter** | **Description** |
|--------------|---------------------------|
| device | SPI device handle |
| send_buf1 | Send data buffer pointer 1 |
| send_length1 | Send data buffer length 1 |
| send_buf2 | Send data buffer pointer 2 |
| send_length2 | Send data buffer length 2 |
| **Return** | —— |
| RT_EOK | Send Successful |
| -RT_EIO | Send Failed |
This function can continuously send data of 2 buffers, ignore the received data, select the CS when send_buf1 is sent, and release the CS after sending send_buf2.
This function is suitable for writing a piece of data to the SPI device, sending data such as commands and addresses for the first time, and sending data of the specified length for the second time. The reason is that it is sent twice instead of being merged into one data block, or `rt_spi_send()`is called twice, because in most data write operations, commands and addresses need to be sent first, and the length is usually only a few bytes. If send it in conjunction with the data that follows, it will need a memory space request and a lot of data handling. If `rt_spi_send()`is called twice, the chip select will be released after the command and address are sent. Most SPI devices rely on setting the chip select once to be the start of the command, so the chip selects the command or address after sending. After the data is released, the operation is discarded.
This function is equivalent to calling `rt_spi_transfer_message()` to transfer 2 messages. The message parameter is configured as follows:
```c
struct rt_spi_message msg1,msg2
msg1.send_buf = send_buf1;
msg1.recv_buf = RT_NULL;
msg1.length = send_length1;
msg1.cs_take = 1;
msg1.cs_release = 0;
msg1.next = &msg2;
msg2.send_buf = send_buf2;
msg2.recv_buf = RT_NULL;
msg2.length = send_length2;
msg2.cs_take = 0;
msg2.cs_release = 1;
msg2.next = RT_NULL;
```
### Receive Data After Sending Data
If need to send data to the slave device first, then receive the data sent from the slave device, and the CS is not released within the process, call the following function to implement:
```c
rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device,
const void *send_buf,
rt_size_t send_length,
void *recv_buf,
rt_size_t recv_length);
```
| **Parameter** | **Description** |
|-------------|--------------------------|
| device | SPI slave device handle |
| send_buf | Send data buffer pointer |
| send_length | Send data buffer length |
| recv_buf | Receive data buffer pointer |
| recv_length | Receive data buffer length |
| **Return** | —— |
| RT_EOK | Successful |
| -RT_EIO | Failed |
This function select CS when sending the first data send_buf when the received data is ignored, and the second data is sent. At this time, the master device will send the data 0XFF, and the received data will be saved in recv_buf, and CS will be released when the function returns.
This function is suitable for reading a piece of data from the SPI slave device. The first time it will send some command and address data, and then receive the data of the specified length.
This function is equivalent to calling `rt_spi_transfer_message()` to transfer 2 messages. The message parameter is configured as follows:
```c
struct rt_spi_message msg1,msg2
msg1.send_buf = send_buf;
msg1.recv_buf = RT_NULL;
msg1.length = send_length;
msg1.cs_take = 1;
msg1.cs_release = 0;
msg1.next = &msg2;
msg2.send_buf = RT_NULL;
msg2.recv_buf = recv_buf;
msg2.length = recv_length;
msg2.cs_take = 0;
msg2.cs_release = 1;
msg2.next = RT_NULL;
```
The SPI device management module also provides `rt_spi_sendrecv8()` and `rt_spi_sendrecv16()` functions, both are wrapper of the `rt_spi_send_then_recv()`. `rt_spi_sendrecv8()` sends a byte data and receives one byte data, and`rt_spi_sendrecv16()` sends 2 bytes. The section data receives 2 bytes of data at the same time.
## Access QSPI Device
The data transfer interface of QSPI is as follows:
| **P**arameter | **Description** |
| -------------------- | ----------------------------|
| rt_qspi_transfer_message() | Transfer message |
| rt_qspi_send_then_recv() | Send then receive |
| rt_qspi_send() | Send data once |
>The QSPI data transfer related interface will call rt_mutex_take(). This function cannot be called in the interrupt service routine, which will cause the assertion to report an error.
### Transfer Data
Transfer messages by the following function:
```c
rt_size_t rt_qspi_transfer_message(struct rt_qspi_device *device, struct rt_qspi_message *message);
```
| **Parameter** | **Description** |
|----------|--------------------------------------------|
| device | QSPI device handle |
| message | Message pointer |
| **Return** | —— |
| Actual transmitted message size | |
The message `structure struct rt_qspi_message` prototype is as follows:
```c
struct rt_qspi_message
{
struct rt_spi_message parent; /* inhert from struct rt_spi_message */
struct
{
rt_uint8_t content; /* Instruction content */
rt_uint8_t qspi_lines; /* Instruction mode, single line mode 1 bit, 2 line mode 2 bits, 4 line mode 4 bits */
} instruction; /* Instruction phase */
struct
{
rt_uint32_t content; /* Address/alternate byte content */
rt_uint8_t size; /* Address/alternate byte size */
rt_uint8_t qspi_lines; /* Address/alternate byte mode, single line mode 1 bit, 2 line mode 2 bits, 4 line mode 4 bits */
} address, alternate_bytes; /* Address/alternate byte stage */
rt_uint32_t dummy_cycles; /* Dummy cycle */
rt_uint8_t qspi_data_lines; /* QSPI data line */
};
```
### Receive Data
Use the following function to receive data:
```c
rt_err_t rt_qspi_send_then_recv(struct rt_qspi_device *device,
const void *send_buf,
rt_size_t send_length,
void *recv_buf,
rt_size_t recv_length);
```
| **Parameter** | **Description** |
|-------------|--------------------------|
| device | QSPI device handle |
| send_buf | Send data buffer pointer |
| send_length | Send data length |
| recv_buf | Receive data buffer pointer |
| recv_length | Receive data length |
| **Return** | —— |
| RT_EOK | Successful |
| Other Errors | Failed |
The send_buf parameter contains the sequence of commands that will be sent.
### Send Data
```c
rt_err_t rt_qspi_send(struct rt_qspi_device *device, const void *send_buf, rt_size_t length)
```
| **Parameter** | **Description** |
|-------------|--------------------------|
| device | QSPI device handle |
| send_buf | Send data buffer pointer |
| length | Send data length |
| **Return** | —— |
| RT_EOK | Successful |
| Other Errors | Failed |
The send_buf parameter contains the sequence of commands and data to be sent.
## Special Usage Scenarios
In some special usage scenarios, a device wants to monopolize the bus for a period of time, and the CS is always valid during the period, during which the data transmission may be intermittent, then the relevant interface can be used as shown. The transfer data function must use `rt_spi_transfer_message()`, and this function must set the cs_take and cs_release of the message to be transmitted to 0 value, because the CS has already used other interface control, and does not need to control during data transmission.
### Acquire the SPI bus
In the case of multi-threading, the same SPI bus may be used in different threads. In order to prevent the data being transmitted by the SPI bus from being lost, the slave device needs to acquire the right to use the SPI bus before starting to transfer data. To transfer data using the bus, use the following function to acquire the SPI bus:
```c
rt_err_t rt_spi_take_bus(struct rt_spi_device *device);
```
| **Parameter** | **Description** |
|----------|---------------|
| device | SPI device handle |
| **Return** | —— |
| RT_EOK | Successful |
| Other Errors | Failed |
### Select CS
After obtaining the usage right of the bus from the device, you need to set the corresponding chip selection signal to be valid. You can use the following function to select the CS:
```c
rt_err_t rt_spi_take(struct rt_spi_device *device)
```
| **Parameter** | **Description** |
|----------|---------------|
| device | SPI device handle |
| **Return** | —— |
| 0 | Successful |
| Other Errors | Failed |
### Add a New Message
When using `rt_spi_transfer_message()` to transfer messages, all messages to be transmitted are connected in the form of a singly linked list. Use the following function to add a new message to be sent to the message list:
```c
void rt_spi_message_append(struct rt_spi_message *list,
struct rt_spi_message *message);
```
| **Parameter** | **Description** |
| ------------- | ----------------------------------- |
| list | Message link node to be transmitted |
| message | New message pointer |
### Release CS
After the device data transfer is completed, CS need to be released. Use the following function to release the CS:
```c
rt_err_t rt_spi_release(struct rt_spi_device *device)
```
| **Parameter** | **D**escription |
|----------|---------------|
| device | SPI device handle |
| Return | —— |
| 0 | Successful |
| Other Errors | Failed |
### Release Data Bus
The slave device does not use the SPI bus to transfer data. The bus must be released as soon as possible so that other slave devices can use the SPI bus to transfer data. The following function can be used to release the bus:
```c
rt_err_t rt_spi_release_bus(struct rt_spi_device *device);
```
| **Parameter** | **Description** |
|----------|---------------|
| device | SPI device handle |
| **Return** | —— |
| RT_EOK | Successful |
## SPI Device Usage Example
The specific use of the SPI device can be referred to the following sample code. The sample code first finds the SPI device to get the device handle, and then uses the rt_spi_transfer_message() send command to read the ID information.
```c
/*
* Program listing: This is a SPI device usage routine
* The routine exports the spi_w25q_sample command to the control terminal
* Command call format: spi_w25q_sample spi10
* Command explanation: The second parameter of the command is the name of the SPI device to be used. If it is empty, the default SPI device is used.
* Program function: read w25q ID data through SPI device
*/
#include <rtthread.h>
#include <rtdevice.h>
#define W25Q_SPI_DEVICE_NAME "qspi10"
static void spi_w25q_sample(int argc, char *argv[])
{
struct rt_spi_device *spi_dev_w25q;
char name[RT_NAME_MAX];
rt_uint8_t w25x_read_id = 0x90;
rt_uint8_t id[5] = {0};
if (argc == 2)
{
rt_strncpy(name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(name, W25Q_SPI_DEVICE_NAME, RT_NAME_MAX);
}
/* Find the spi device to get the device handle */
spi_dev_w25q = (struct rt_spi_device *)rt_device_find(name);
if (!spi_dev_w25q)
{
rt_kprintf("spi sample run failed! can't find %s device!\n", name);
}
else
{
/* Method 1: Send the command to read the ID using rt_spi_send_then_recv() */
rt_spi_send_then_recv(spi_dev_w25q, &w25x_read_id, 1, id, 5);
rt_kprintf("use rt_spi_send_then_recv() read w25q ID is:%x%x\n", id[3], id[4]);
/* Method 2: Send the command to read the ID using rt_spi_transfer_message() */
struct rt_spi_message msg1, msg2;
msg1.send_buf = &w25x_read_id;
msg1.recv_buf = RT_NULL;
msg1.length = 1;
msg1.cs_take = 1;
msg1.cs_release = 0;
msg1.next = &msg2;
msg2.send_buf = RT_NULL;
msg2.recv_buf = id;
msg2.length = 5;
msg2.cs_take = 0;
msg2.cs_release = 1;
msg2.next = RT_NULL;
rt_spi_transfer_message(spi_dev_w25q, &msg1);
rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[3], id[4]);
}
}
/* Export to the msh command list */
MSH_CMD_EXPORT(spi_w25q_sample, spi w25q sample);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,646 @@
# UART Device
## UART Introduction
UART (Universal Asynchronous Receiver/Transmitter), as a kind of asynchronous serial communication protocol, the working principle is to transmit each character of the transmitted data one by one. It is the most frequently used data bus during application development.
The UART serial port is characterized by sequentially transmitting data one bit at a time. As long as two transmission lines can realize two-way communication, one line transmits data while the other receives data . There are several important functions for UART serial communication, namely baud rate, start bit, data bit, stop bit and parity bit. For two ports that use UART serial port communication, these functions must be matched, otherwise the communication can't be carried out normally. The data format of the UART serial port transmission is as shown below:
![Serial Transmission Data Format](figures/uart1.png)
* Start bit: Indicates the start of data transfer and the level logic is "0".
- Data bits: Possible values are 5, 6, 7, 8, and 9, indicating that these bits are transmitted. The value is generally 8, because an ASCII character value is 8 bits.
- Parity check bit: It it used by the receiver to verify the received data. The number of bits is used in the check of "1" is even (even parity) or odd (odd parity) ,in order to verify the data transmission. It is also fine by not using this bit .
- Stop Bit: Indicates the end of one frame of data. The level logic is "1".
- Baudrate: It is the rate at which a serial port communicates, which expressed in bits per second (bps) of the binary code transmitted in unit time. The common baud rate values are 4800, 9600, 14400, 38400, 115200, etc. The higher the value is, the faster the data transmission will be.
## Access UART Device
The application accesses the serial port hardware through the I/O device management interface provided by RT-Thread. The related interfaces are as follows:
| **Funtion** | **Description** |
| --------------------------- | -------------------------- |
| rt_device_find() | find device |
| rt_device_open() | open device |
| rt_device_read() | read device |
| rt_device_write() |write device|
| rt_device_control() | control device |
| rt_device_set_rx_indicate() | set receive callback function |
| rt_device_set_tx_complete() | set send complete callback function |
| rt_device_close() | close device |
### Find UART Device
The application obtains the device handle according to the uart device name, and then can operate the uart device.The device find function is shown below
```c
rt_device_t rt_device_find(const char* name);
```
| **Parameter** | **Description** |
| ------------- | ------------------------------------------------------------ |
| name | device's name |
| **back** | —— |
| device handle | finding the corresponding device will return to the corresponding device handle |
| RT_NULL | corresponding device object was not found |
Generally, the name of the uart device registered to the system is uart0, uart1, etc. samples are as follows:
```c
#define SAMPLE_UART_NAME "uart2" /* uart device name */
static rt_device_t serial; /* uart device handle */
/* Find uart device*/
serial = rt_device_find(SAMPLE_UART_NAME);
```
### Open UART Device
Through the device handle, the application can open and close the device. When the device is opened, it will detect whether the device has been initialized. If it is not initialized, it will call the initialization interface to initialize the device by default. Open the device through the following functions:
```c
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
```
| **Parameter** | **Description** |
| ---------- | ------------------------------- |
| dev | device handle |
| oflags | device mode flags |
| **back** | —— |
| RT_EOK | device opened successfully |
| -RT_EBUSY | If the standalone parameter RT_DEVICE_FLAG_STANDALONE is included in the functions specified when the device is registered, the device will not be allowed to be opened repeatedly |
| Other error codes | device failed to open |
oflags parameters support the following values (Use OR logic to support multiple values):
```c
#define RT_DEVICE_FLAG_STREAM 0x040 /* Stream mode */
/* Receive mode function */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* Interrupt receive mode */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA receiving mode */
/* Receive mode function */
#define RT_DEVICE_FLAG_INT_TX 0x400 /* Interrupt receive mode*/
#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA receive mode */
```
There are three modes of uart data receiving and sending: interrupt mode, polling mode and DMA mode. When used, only one of the three modes can be selected. If the open parameter oflag of the serial port does not specify the use of interrupt mode or DMA mode, the polling mode is used by default.
The DMA (Direct Memory Access) transfer mode does not require the CPU to directly control the transfer, and does not have the process of reserving the scene and restoring the scene as they have in the interrupt processing mode. The DMA controller opens a path for directly transferring data to the RAM and the I/O device, which saves CPU resources to do other things. Using DMA transfer can continuously acquire or send a piece of information without taking up interrupts or delays, which is useful when communication is frequent or when large pieces of information are to be transmitted.
>RT_DEVICE_FLAG_STREAM: Stream mode is used to output a string to the serial terminal: when the output character is `"\n"` (corresponding to the hexadecimal value 0x0A), a ``\r"` is automatically output in front (corresponding to hexadecimal value is 0x0D).
The stream mode `RT_DEVICE_FLAG_STREAM` can be used with the receive and send mode parameter with the "|" logic.
An example of using a uart device in **interrupt receive mode and polling mode** as follows:
```c
#define SAMPLE_UART_NAME "uart2" /* uart device name */
static rt_device_t serial; /* uart device handle */
/* find uart device */
serial = rt_device_find(SAMPLE_UART_NAME);
/* Open the uart device in interrupt receive mode and polling mode*/
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
```
If the uart is to use the DMA receive mode, the oflags takes the value RT_DEVICE_FLAG_DMA_RX. An example of using a uart device in the **DMA receive and polling send mode** is as follows:
```c
#define SAMPLE_UART_NAME "uart2" /* uart device's name */
static rt_device_t serial; /* uart device handle */
/* find uart device */
serial = rt_device_find(SAMPLE_UART_NAME);
/* Open the uart device in DMA receive and polling send mode*/
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
```
### Control UART Device
Through command control word, the application can configure the uart device by the following function:
```c
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
```
| **Parameter** | **Description** |
| ----------------- | ------------------------------------------------------------ |
| dev | device handle |
| cmd | command control word can be valued asRT_DEVICE_CTRL_CONFIG |
| arg | controlled parameter struct serial_configure |
| **Back** | —— |
| RT_EOK | function executed successfully |
| -RT_ENOSYS | execution failed, dev is empty |
| Other error codes | execution failed |
* The prototype of control parameter structure: struct serial_configure is as follows
```c
struct serial_configure
{
rt_uint32_t baud_rate; /* Baudrate */
rt_uint32_t data_bits :4; /* Data bit */
rt_uint32_t stop_bits :2; /* Stop bit */
rt_uint32_t parity :2; /* Parity bit */
rt_uint32_t bit_order :1; /* Prioritized by order */
rt_uint32_t invert :1; /* Mode */
rt_uint32_t bufsz :16; /* Receive data buffer size */
rt_uint32_t reserved :4; /* Reserved bit */
};
```
* The default macro configuration provided by RT-Thread is as follows
```c
#define RT_SERIAL_CONFIG_DEFAULT \
{ \
BAUD_RATE_115200, /* 115200 bps */ \
DATA_BITS_8, /* 8 databits */ \
STOP_BITS_1, /* 1 stopbit */ \
PARITY_NONE, /* No parity */ \
BIT_ORDER_LSB, /* LSB first sent */ \
NRZ_NORMAL, /* Normal mode */ \
RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
0 \
}
```
The configuration parameters provided by RT-Thread can be defined as the following macro definitions:
```c
/* The baudrate can be defined as*/
#define BAUD_RATE_2400 2400
#define BAUD_RATE_4800 4800
#define BAUD_RATE_9600 9600
#define BAUD_RATE_19200 19200
#define BAUD_RATE_38400 38400
#define BAUD_RATE_57600 57600
#define BAUD_RATE_115200 115200
#define BAUD_RATE_230400 230400
#define BAUD_RATE_460800 460800
#define BAUD_RATE_921600 921600
#define BAUD_RATE_2000000 2000000
#define BAUD_RATE_3000000 3000000
/* Data bits can be defined as*/
#define DATA_BITS_5 5
#define DATA_BITS_6 6
#define DATA_BITS_7 7
#define DATA_BITS_8 8
#define DATA_BITS_9 9
/* Stop bits can be defined as */
#define STOP_BITS_1 0
#define STOP_BITS_2 1
#define STOP_BITS_3 2
#define STOP_BITS_4 3
/* Parity bits can be defined as */
#define PARITY_NONE 0
#define PARITY_ODD 1
#define PARITY_EVEN 2
/* Bit order can be defined as */
#define BIT_ORDER_LSB 0
#define BIT_ORDER_MSB 1
/* Mode canbe defined as */
#define NRZ_NORMAL 0 /* normal mode */
#define NRZ_INVERTED 1 /* inverted mode */
/* Default size of the receive data buffer */
#define RT_SERIAL_RB_BUFSZ 64
```
**Receive Buffer**
When the uart device is opened using interrupt receive mode, the uart driver framework will open a buffer according to the size of RT_SERIAL_RB_BUFSZ to save the received data. When the underlying driver receives a data, it will put the data into the buffer in the interrupt service program.
>The default size of the receive data buffer is 64 bytes. If the number of received data in one-time is too large and the data is not read in time, the data of the buffer will be overwritten by the newly received data, resulting in data loss. It is recommended to increase the buffer.
A sample for configuring uart hardware parameters such as data bits, check bits, stop bits, and so on are shown below
```c
#define SAMPLE_UART_NAME "uart2" /* uart device's name */
static rt_device_t serial; /* uart device handle */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* Configuration parameters */
/* Find uart devices */
serial = rt_device_find(SAMPLE_UART_NAME);
/* Open the uart device in interrupt receive and polling send mode */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
config.baud_rate = BAUD_RATE_115200;
config.data_bits = DATA_BITS_8;
config.stop_bits = STOP_BITS_2;
config.parity = PARITY_NONE;
/* The serial port configuration parameters can only be modified after opening the device */
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
```
### Send Data
To write data to the serial port, the following functions can be used:
```c
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
```
| **Parameter** | **Description** |
| ---------- | ------------------------------------------ |
| dev | device handle |
| pos | Write data offset, this parameter is not used in uart device |
| buffer | Memory buffer pointer, place the data to be written |
| size | The size of the written data |
| **back** | —— |
| The actual size of the written data | If it is a character device, the return size is in bytes; |
| 0 | It needs to read the current thread's errno to determine the error status |
Calling this function will write the data in the `buffer` to the `dev` device, the size of the write data is: size.
The sample program for writing data to the serial port is as follows:
```c
#define SAMPLE_UART_NAME "uart2" /* uart device's name */
static rt_device_t serial; /* uart device handle */
char str[] = "hello RT-Thread!\r\n";
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* Configuration parameter */
/*find uart device */
serial = rt_device_find(SAMPLE_UART_NAME);
/* Open the uart device in interrupt reception and polling mode */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* Send string */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
```
### Set The Send completion Callback Function
When the application calls `rt_device_write()` to write data, if the underlying hardware can support automatic transmission, the upper application can set a callback function. This callback function is called after the underlying hardware data has been sent (for example, when the DMA transfer is complete or the FIFO has been written to complete the completion interrupt). You can set the device to send completion instructions by the following function:
```c
rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
```
| **Parameter** | **Description** |
| ------------- | ------------------------- |
| dev | device handle |
| tx_done | callback function pointer |
| **back** | —— |
| RT_EOK | set up successfully |
When this function is called, the callback function is provided by the user. When the hardware device sends the data, the device driver calls back this function and passes the sent data block address buffer as a parameter to the upper application. When the application (thread) receives the indication, it will release the buffer memory block or use it as the buffer for the next write data according to the condition of sending the buffer.
### Set The Receive Callback Function
The data receiving instruction can be set by the following function. When the serial port receives the data, it will inform the upper application thread that the data has arrived:
```c
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
```
| **Parameter** | **Description** |
| -------- | ------------ |
| dev | device handle |
| rx_ind | callback function pointer |
| dev | device handle (callback function parameter) |
| size | buffer data size (callback function parameter) |
| **back** | —— |
| RT_EOK | set up successfully |
The callback function for this function is provided by the user. If the uart device is opened in interrupt receive mode, the callback function will be called when the serial port receives a data, and the data size of the buffer will be placed in the `size` parameter, and the uart device handle will be placed in the `dev` parameter.
If the uart is opened in DMA receive mode, the callback function is called when the DMA completes receiving a batch of data.
Normally the receiving callback function can send a semaphore or event to notify the serial port data processing thread that data has arrived. The example is as follows:
```c
#define SAMPLE_UART_NAME "uart2" /* uart device name */
static rt_device_t serial; /* uart device handle */
static struct rt_semaphore rx_sem; /* The semaphore used to receive the message */
/* Receive data callback function */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* When the serial port receives the data, it triggers interrupts, calls this callback function, and sends the received semaphore */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static int uart_sample(int argc, char *argv[])
{
serial = rt_device_find(SAMPLE_UART_NAME);
/* Open the uart device in interrupting receive mode */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* Initialization semaphore */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* Set the receive callback function */
rt_device_set_rx_indicate(serial, uart_input);
}
```
### Receive Data
You can call the following function to read the data received by the uart:
```c
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
```
| **Parameter** | **Description** |
| -------------------------------- | ------------------------------------------------------------ |
| dev | device handle |
| pos | Read data offset, uart device dose not use this parameter |
| buffer | Buffer pointer, the data read will be saved in the buffer |
| size | Read the size of the data |
| **back** | —— |
| Read the actual size of the data | If it is a character device, the return size is in bytes. |
| 0 | It needs to read the current thread's errno to determine the error status |
Read data offset: pos is not valid for character devices. This parameter is mainly used for block devices.
An example of using the interrupt receive mode with the receive callback function is as follows:
```c
static rt_device_t serial; /* uart device handle */
static struct rt_semaphore rx_sem; /* Semaphore used to receive messages */
/* Thread receiving data */
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* Reads a byte of data from the serial port and waits for the receiving semaphore if it is not read */
while (rt_device_read(serial, -1, &ch, 1) != 1)
{
/* Blocking waiting to receive semaphore, waiting for the semaphore to read the data again*/
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* Read the data through the serial port dislocation output*/
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}
```
### Close The UART Device
After the application completes the serial port operation, the uart device can be closed by the following functions:
```c
rt_err_t rt_device_close(rt_device_t dev);
```
| **Parameter** | **Description** |
| ----------------- | ------------------------------------------------------------ |
| dev | device handle |
| **back** | —— |
| RT_EOK | device closed successfully |
| -RT_ERROR | The device has been completely shut down and cannot be shut down repeatedly |
| other error codes | fail to close the device |
Use the `rt_device_close()` interface and `rt_device_open()` interface in pair. When you open the device, you need to close the device once, so that the device will be completely shut down, otherwise the device will remain open.
## Examples Of Using UART Device
### Interrupt Receiving And Polling Send
The main steps of the sample code are as follows:
1. First find the uart device to get the device handle.
2. Initialize the semaphore that the callback function sends, and then open the uart device in read/write and interrupt receive mode.
3. Set the receive callback function of the uart device, then send the string and create a read data thread.
4. The read data thread will try to read a character data. If there is no data, it will hang and wait for the semaphore. When the uart device receives a data, it will trigger an interrupt and call the receive callback function. This function will send a semaphore to wake up the thread. At this point, the thread will immediately read the received data.
5. This sample code is not limited to a specific BSP. According to the uart device registered by BSP, modify the uart device's name corresponding to the sample code's macro definition SAMPLE_UART_NAME to run.
The running sequence diagram is shown as follows:
![Serial Port Interrupt Reception and Polling Transmission Sequence Diagram](figures/uart-int.png)
```c
/*
* Program list: This is a uart device usage routine
* The routine exports the uart_sample command to the control terminal
* Format of command: uart_sample uart2
* Command explanation: the second parameter of the command is the name of the uart device. If it is null, the default uart device wil be used
* Program function: output the string "hello RT-Thread!" through the serial port, and then malposition the input character
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart2"
/* Semaphore used to receive messages */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
/* Receive data callback function */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* After the uart device receives the data, it generates an interrupt, calls this callback function, and then sends the received semaphore. */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* Read a byte of data from the serial port and wait for the receiving semaphore if it is not read */
while (rt_device_read(serial, -1, &ch, 1) != 1)
{
/* Being Suspended and waiting for the semaphore */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* Read the data from the serial port and output through dislocation */
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}
static int uart_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* Find uart devices in the system */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* Initialize the semaphore */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* Open the uart device in interrupt receive and polling send mode */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* Set the receive callback function */
rt_device_set_rx_indicate(serial, uart_input);
/* Send string */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* Create a serial thread */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* Start the thread successfully */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* Export to the msh command list */
MSH_CMD_EXPORT(uart_sample, uart device sample);
```
### DMA Reception And Polling Transmission
When the serial port receives a batch of data, it will call the receive callback function. The receive callback function will send the data size of the buffer at this time to the waiting data processing thread through the message queue. After the thread gets the message, it is activated and reads the data. In general, the DMA receive mode completes data reception in conjunction with the DMA receive completion interrupt and the serial port idle interrupt.
* This sample code is not limited to a specific BSP. According to the uart device registered by BSP, modify the sample code macro to define the uart device name corresponding to SAMPLE_UART_NAME to run.
The running sequence diagram is shown below:
![Serial DMA Receiving and Polling Transmission Sequence Diagram](figures/uart-dma.png)
```c
/*
* Program list: This is a uart device DMA receive usage routine
* The routine exports the uart_dma_sample command to the control terminal
* Command format: uart_dma_sample uart3
* Command explanation: The second parameter of the command is the name of the uart device to be used. If it is empty, the default uart device will be used.
* Program function: output the string "hello RT-Thread!" through the serial port, and output the received data through the serial port, and then print the received data.
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart3" /* uart device name */
/* Serial port receiving message structure*/
struct rx_msg
{
rt_device_t dev;
rt_size_t size;
};
/* uart device handle */
static rt_device_t serial;
/* Message queue control block*/
static struct rt_messagequeue rx_mq;
/* Receive data callback function */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
struct rx_msg msg;
rt_err_t result;
msg.dev = dev;
msg.size = size;
result = rt_mq_send(&rx_mq, &msg, sizeof(msg));
if ( result == -RT_EFULL)
{
/* message queue full */
rt_kprintf("message queue full\n");
}
return result;
}
static void serial_thread_entry(void *parameter)
{
struct rx_msg msg;
rt_err_t result;
rt_uint32_t rx_length;
static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];
while (1)
{
rt_memset(&msg, 0, sizeof(msg));
/* Read messages from the message queue*/
result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
if (result == RT_EOK)
{
/*Read data from the serial port*/
rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
rx_buffer[rx_length] = '\0';
/* Output the read message through the uart device: serial */
rt_device_write(serial, 0, rx_buffer, rx_length);
/* Print data */
rt_kprintf("%s\n",rx_buffer);
}
}
}
static int uart_dma_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
static char msg_pool[256];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* find uart device */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* Initialize message queue */
rt_mq_init(&rx_mq, "rx_mq",
msg_pool, /* a pool for storing messages */
sizeof(struct rx_msg), /* The maximum length of a message*/
sizeof(msg_pool), /* The size of the message pool */
RT_IPC_FLAG_FIFO); /* If there are multiple threads waiting, assign messages according to the order. */
/* Open the uart device in DMA receive and polling send mode */
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
/* Set the receive callback function */
rt_device_set_rx_indicate(serial, uart_input);
/* Send string */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* Create a thread */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* Start the thread if it is created successfully*/
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* Export to the msh command list */
MSH_CMD_EXPORT(uart_dma_sample, uart device dma sample);
```

View File

@ -0,0 +1,228 @@
# WATCHDOG Device
## An Introduction to WATCHDOG
The hardware watchdog timer is a timer whose timing output is connected to the reset terminal of the circuit. In a productized embedded system, in order to automatically reset the system under abnormal conditions, it generally needs a watchdog.
When the watchdog was started, the counter starts counting automatically. If it is not reset counter value before the counter overflows, the counter overflow will generate a reset signal to the CPU to restart the system. When the system is running normally, it is necessary to clear the watchdog counter within the time interval allowed by the watchdog (commonly known as "feeding the dog"), and the reset signal will not be generated. If the program can "feed the dog" on timethe system does not go wrongotherwise the system will reset.
In general, users can feed the dog in the idlehook function and key function of RT-Thread.
## Access to the WATCHDOG Device
The application accesses the watchdog hardware through the I/O device management interface provided by RT-Thread. The related interfaces are as follows:
| **Function** | **Description** |
| ---------------- | ---------------------------------- |
| rt_device_find() | Find the device handle based on the device name of the watchdog device |
| rt_device_init() | Initialize the watchdog device |
| rt_device_control() |Control the watchdog device |
| rt_device_close() | Close the watchdog device |
### Find the Watchdog Device
The application obtains the device handle based on the watchdog device's name, and then it can operate the watchdog device. The function for finding a device is as follows:
```c
rt_device_t rt_device_find(const char* name);
```
| **Function** | **Description** |
| -------- | ---------------------------------- |
| name | the name of the watchdog device |
| **return** | —— |
| device handle | finding the corresponding device and then return to the corresponding device handle |
| RT_NULL | no corresponding device object found |
An usage example is as follows:
```c
#define IWDG_DEVICE_NAME "iwg" /* the name of the watchdog device */
static rt_device_t wdg_dev; /* device handle of the watchdog */
/* find the watchdog device based on the device's name and obtain the device handle */
wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
```
### Initialize the Watchdog Device
The watchdog device need to be initialized before using, which can be done by the following function:
```c
rt_err_t rt_device_init(rt_device_t dev);
```
| **Function** | **Description** |
| ---------- | ------------------------------- |
| dev | handle of the watchdog device |
| **return** | —— |
| RT_EOK | the device succeeded initializing |
| -RT_ENOSYS | initialization failed, the watchdog device driver initialization function is empty |
| other error code | the device failed to open |
An example is as follows:
```c
#define IWDG_DEVICE_NAME "iwg" /* the name of the watchdog device */
static rt_device_t wdg_dev; /* handle of the watchdog device */
/* find the watchdog device based on the device's name and obtain the device handle */
wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
/* initialize the device */
rt_device_init(wdg_dev);
```
### Control the Watchdog Device
The application can configure the watchdog device using the command control word, which can be done by the following function:
```c
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
```
| **Function** | **Description** |
| ---------------- | ---------------------------------- |
| dev | handle of the watchdog device |
| cmd | the command word |
| arg | controlled parameter |
| **return** | —— |
| RT_EOK | function executed successfully |
| -RT_ENOSYS | execution failed, the dev is empty |
| other error code | execution failed |
The command control word `'cmd'` can take the following macro definition values:
```c
#define RT_DEVICE_CTRL_WDT_GET_TIMEOUT (1) /* get the overflow time */
#define RT_DEVICE_CTRL_WDT_SET_TIMEOUT (2) /* set the overflow time */
#define RT_DEVICE_CTRL_WDT_GET_TIMELEFT (3) /* get the remaining time */
#define RT_DEVICE_CTRL_WDT_KEEPALIVE (4) /* feed the dog */
#define RT_DEVICE_CTRL_WDT_START (5) /* start the watchdog */
#define RT_DEVICE_CTRL_WDT_STOP (6) /* stop the watchdog */
```
An example of setting the overflow time of the watchdog is as follows:
```c
#define IWDG_DEVICE_NAME "iwg" /* the name of the watchdog device */
rt_uint32_t timeout = 1000; /* the overflow time */
static rt_device_t wdg_dev; /* handle of the watchdog device */
/* find the watchdog device based on the device's name and obtain the device handle */
wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
/* initialize the device */
rt_device_init(wdg_dev);
/* set the overflow time of the watch dog */
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, (void *)timeout);
/* set idle-hook function */
rt_thread_idle_sethook(idle_hook);
```
An example of feeding a dog in an idle thread hook function is as follows:
```c
static void idle_hook(void)
{
/* Feed the dog in the callback function of the idle thread */
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
}
```
### Close the Watchdog Device
When the application completes the operation of the watchdog, it can close the watchdog device:
```c
rt_err_t rt_device_close(rt_device_t dev);
```
| **Function** | **Description** |
| ---------- | ---------------------------------- |
| dev | handle of the watchdog device |
| **return** | —— |
| RT_EOK | close the device successfully |
| -RT_ERROR | The device has been completely shut down and cannot be closed repeatedly |
| other error code | fail to close the device |
Closing the device interface and opening the device interface need to match each other. When you open the device, you need to close the device once correspondingly, so that the device will be completely shut down, otherwise the device will remain unclosed.
## Watchdog Device usage example
The specific use of the watchdog device can be referred to the following sample code. The main steps of the sample code are as follows:
1. First find the device handle based on the device name "iwg".
2. Set the overflow time of the watchdog after initializing the device.
3. Set the idle thread callback function.
4. This callback function will run and feed the dog when the system executes idle threads.
```c
/*
* Program list: This is an independent watchdog device usage routine
* The routine exports the iwdg_sample command to the control terminal
* Command call format: iwdg_sample iwg
* Command explanation: The second parameter of the command is the name of the watchdog device to be used. If it is empty, you can use the default watchdog device of the routine.
* Program function: The program finds the watchdog device through the device's name, and then initializes the device and sets the overflow time of the watchdog device.
* Then set the idle thread callback function, which will feed the dog in the idle callback function.
*/
#include <rtthread.h>
#include <rtdevice.h>
#define IWDG_DEVICE_NAME "iwg" /* the name of the watchdog device */
static rt_device_t wdg_dev; /* handle of the watchdog device */
static void idle_hook(void)
{
/* feed the dog in the callback function */
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
rt_kprintf("feed the dog!\n ");
}
static int iwdg_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
rt_uint32_t timeout = 1000; /* the overflow time */
char device_name[RT_NAME_MAX];
/* Determine if the command-line parameter is given the device name */
if (argc == 2)
{
rt_strncpy(device_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(device_name, IWDG_DEVICE_NAME, RT_NAME_MAX);
}
/* find the watchdog device based on the device's name and obtain the device handle */
wdg_dev = rt_device_find(device_name);
if (!wdg_dev)
{
rt_kprintf("find %s failed!\n", device_name);
return RT_ERROR;
}
/* initialize the device */
ret = rt_device_init(wdg_dev);
if (ret != RT_EOK)
{
rt_kprintf("initialize %s failed!\n", device_name);
return RT_ERROR;
}
/* set the overflow time of the watch dog */
ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
if (ret != RT_EOK)
{
rt_kprintf("set %s timeout failed!\n", device_name);
return RT_ERROR;
}
/* set idle thread callback function */
rt_thread_idle_sethook(idle_hook);
return ret;
}
/* export to the msh command list */
MSH_CMD_EXPORT(iwdg_sample, iwdg sample);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,443 @@
# WLAN Device
With the rapid development of the Internet of Things, more and more embedded devices are equipped with WIFI wireless network devices. In order to be able to manage WIFI network devices, RT-Thread introduces a WLAN device management framework. This framework has many features to control and manage WIFI, providing developers with many conveniences for using WIFI devices.
## Introduction to the WLAN Framework
The WLAN framework is a set of middleware developed by RT-Thread for managing WIFI. Connect to the specific WIFI driver, control the WIFI connection disconnection, scan and other operations. Support different applications, provide WIFI control, events, data diversion and other operations for the application, and provide a unified WIFI control interface for the upper application. The WLAN framework consists of three main parts. The DEV driver interface layer provides a unified API for the WLAN framework. Manage layer provides users with specific functions such as WIFI scanning, connection, and disconnection. Protocol is responsible for processing the data stream generated on the WIFI. Different protocols such as LWIP can be mounted according to different usage scenarios. It has the characteristics of simple use, complete functions, convenient docking and strong compatibility.
The following figure is a hierarchical diagram of the WIFI framework:
![WIFI Framework](figures/an0026_1.png)
The First Part: `APP`, the application layer. It is a specific application based on the WLAN framework, such as WiFi-related shell commands.
The Second Part: `Airkiss and Voice`, the network configuration layer. Provide functions such as using wireless or sound waves to configure the network.
The Third Part: `WLAN Manager`, the WLAN management layer. Ability to control and manage WLAN devices. It has functions related to WLAN control, such as setting mode, connecting hotspots, disconnecting hotspots, enabling hotspots, scanning hotspots, etc. It also provides management functions such as reconnection after disconnection and automatic hotspot switching.
The Fourth Part: `WLAN Protocol`, the protocol layer. The data stream is submitted to a specific protocol for resolution, and the user can specify to communicate using different protocols.
The Fifth Part: `WLAN Config`, the parameter management layer. Manage hotspot information and passwords for successful connections and write them to non-volatile storage media.
The Sixth Part: `WLAN Device`, the driver interface layer. Connect to specific WLAN hardware and provide unified APIs for management.
### Functions
* Automatic Connection: After using automatic connection function, as long as the WIFI is disconnected, the hotspot information of the previous successful connection will be automatically read, and the hotspot will be connected. If a hotspot connection fails, switch to the next hotspot to connect until the connection is successful. The hotspot information used by the automatic connection is sequentially tried in the order of the success of the connection, and the hotspot information of the latest connection success is preferentially used. After the connection is successful, the hotspot information is cached first, and use it first when reconnecting after the next disconnection.
* Parameter storage: Stores the WIFI parameters for successful connection. The WIFI parameter will be cached in the memory. If the external non-volatile storage interface is configured, it will be stored in the external storage medium. Users can implement the `struct rt_wlan_cfg_ops` structure according to his actual situation and save the parameters anywhere. The cached parameters mainly provide hotspot information for automatic connections. When WIFI is unconnected, it will read the cached parameters and try to connect.
* WIFI control: Provide complete WIFI control APIs, scanning, connection, hotspot, etc. Provide WIFI related status callback events, disconnect, connection, connection failure, etc. Provide users with an easy to use WIFI management APIs.
* Shell command: You can enter the command in Msh to control WIFI to perform scanning, connecting, disconnecting and other actions. Print debugging information such as WIFI status.
### Configuration
Use `menuconfig` command in ENV to enter the WLAN configuration interface by following the menu:
```c
RT-Thread Components -> Device Drivers -> Using WiFi ->
```
Configuration options are described in detail as follows:
```c
[*] Using Wi-Fi framework /* Using Wi-Fi framework */
(wlan0) The WiFi device name for station /* The default name for station */
(wlan1) The WiFi device name for ap /* The default name for ap */
(lwip) Default transport protocol /* Default protocol */
(10000) Set scan timeout time(ms) /* Scan timeout time */
(10000) Set connect timeout time(ms) /* Connect timeout time */
(32) SSID name maximum length /* Maximum length of SSID name */
(32) Maximum password length /* Maximum length of password */
[*] Automatic sorting of scan results /* Automatic sorting of scan results */
(3) Maximum number of WiFi information automatically saved /* Maximum number of WiFi information automatically saved */
(wlan_job) WiFi work queue thread name /* WiFi work queue thread name */
(2048) wifi work queue thread size /* wifi work queue thread size */
(22) WiFi work queue thread priority /* WiFi work queue thread priority */
(2) Maximum number of driver events /* Maximum number of driver events in dev layer */
[ ] Forced use of PBUF transmission /* Forced use of PBUF transmission */
[ ] Enable WLAN Debugging Options /* Enable WLAN Debugging Options */
```
## Access Wi-Fi Devices
The application accesses the WLAN device hardware through the WLAN device management interface, and the relevant interfaces are as follows:
| Fuctions | **Description** |
| -------------------- | ---------------------------- |
| rt_wlan_prot_attach() | Specify the WLAN protocol attached |
| rt_wlan_scan_sync() | Synchronized WLAN Scan |
| rt_wlan_connect() | Synchronized Hotspot Connection |
| rt_wlan_disconnect() | Synchronized Hotspot Disconnection |
| rt_wlan_config_autoreconnect() | Configuration automatic reconnection mode |
### Specify Protocol
```c
rt_err_t rt_wlan_prot_attach(const char *dev_name, const char *prot_name);
```
| **Parameter** | **D**escription |
| ----------------------------- | ---------------------------------- |
| dev_name | WLAN device name |
| prot_name | Protocol name, possible values: RT_WLAN_PROT_LWIP, indicates the protocol type LWIP |
| Return | **--** |
| -RT_ERROR | Execution failed |
| RT_EOK | Execution succeed |
### Synchronized WLAN Scan
```c
struct rt_wlan_scan_result *rt_wlan_scan_sync(void);
```
| **Return** | **Description** |
| ---------- | ------------------------------- |
| rt_wlan_scan_result | Scan Result |
The scan result is a structure as follows:
```c
struct rt_wlan_scan_result
{
rt_int32_t num; /* info number */
struct rt_wlan_info *info; /* info pointer */
};
```
### Synchronized Hotspot Connection
```c
rt_err_t rt_wlan_connect(const char *ssid, const char *password);
```
| **Parameter** | **Description** |
| ----------------------------- | ---------------------------------- |
| ssid | WIFI name |
| password | WIFI password |
| Return | **--** |
| -RT_EINVAL | Parameter error |
| -RT_EIO | Unregistered device |
| -RT_ERROR | Connection failed |
| RT_EOK | Connection successful |
### Synchronized Hotspot Disconnection
```c
rt_err_t rt_wlan_disconnect(void);
```
| Return | **Description** |
| ----------------------------- | ---------------------------------- |
| -RT_EIO | Unregistered device |
| -RT_ENOMEM | Not enough memory |
| -RT_ERROR | Disconnection failed |
| RT_EOK | Disconnection successful |
### Automatic Reconnection Mode Configuration
```c
void rt_wlan_config_autoreconnect(rt_bool_t enable);
```
| **P**arameter | **Description** |
| ----------------------------- | ---------------------------------- |
| enable | enable or disable automatic reconnection |
## FinSH Command
Using shell commands can help us quickly debug WiFi-related features. The wifi related shell commands are as follows:
```c
wifi /* Print help */
wifi help /* View help */
wifi join SSID [PASSWORD] /* Connect wifi.if SSDI is empty, use configuration to connect automatically */
wifi ap SSID [PASSWORD] /* Create hotspot */
wifi scan /* Scan all hotspots */
wifi disc /* Disconnnect */
wifi ap_stop /* Stop hotspot */
wifi status /* Print wifi status sta + ap */
wifi smartconfig /* Start to configure network function */
```
### WiFi Scan
The wifi scan command is `wifi scan`. After the wifi scan command is executed, the surrounding hotspot information will be printed on the terminal. Through the printed hotspot information, you can see multiple attributes such as SSID and MAC address.
Enter the command in msh and the scan results are as follows:
```c
wifi scan
SSID MAC security rssi chn Mbps
------------------------------- ----------------- -------------- ---- --- ----
rtt_test_ssid_1 c0:3d:46:00:3e:aa OPEN -14 8 300
test_ssid 3c:f5:91:8e:4c:79 WPA2_AES_PSK -18 6 72
rtt_test_ssid_2 ec:88:8f:88:aa:9a WPA2_MIXED_PSK -47 6 144
rtt_test_ssid_3 c0:3d:46:00:41:ca WPA2_MIXED_PSK -48 3 300
```
### WiFi Connection
The wifi scan command is `wifi join`. The command needs to be followed by the hotspot name and hotspot password. If the hotspot does not have a password, you may not enter this item. After the WiFi connection command is executed, if the hotspot exists and the password is correct, the board will connect to the hotspot and obtain the IP address. After the network connection is successful, you can use `socket` sockets for network communication.
An example of using the wifi connection command is as follows. After the connection is successful, the obtained IP address will be printed on the terminal as follows:
```c
wifi join ssid_test 12345678
[I/WLAN.mgnt] wifi connect success ssid:ssid_test
[I/WLAN.lwip] Got IP address : 192.168.1.110
```
### WiFi Disconnection
The command to disconnect WiFi is `wifi disc`. After the WiFi disconnect command is executed, the development board will disconnect from the hotspot.
The WiFi disconnect command usage example is as follows. After the disconnection is successful, the following information will be printed on the terminal as shown below.
```c
wifi disc
[I/WLAN.mgnt] disconnect success!
```
## Example for WLAN Device Usage
### WiFi Scan
The following code will show a WiFi sync scan, and then print the results on the terminal. First perform WIFI initialization, and then execute the WIFI scan function `rt_wlan_scan_sync`, this function is synchronous, the number of scans and results returned by the function. In this example, the scanned hotspot name will be printed.
```c
#include <rthw.h>
#include <rtthread.h>
#include <wlan_mgnt.h>
#include <wlan_prot.h>
#include <wlan_cfg.h>
void wifi_scan(void)
{
struct rt_wlan_scan_result *result;
int i = 0;
/* Configuring WLAN device working mode */
rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION);
/* WiFi scan */
result = rt_wlan_scan_sync();
/* Print scan results */
rt_kprintf("scan num:%d\n", result->num);
for (i = 0; i < result->num; i++)
{
rt_kprintf("ssid:%s\n", result->info[i].ssid.val);
}
}
int scan(int argc, char *argv[])
{
wifi_scan();
return 0;
}
MSH_CMD_EXPORT(scan, scan test.);
```
The results are as follows:
![Scan](figures/an0026_3.png)
### WiFi Connection and Disconnection
The code below will show a WiFi sync connection. Initialize WIFI first, and then create a semaphore for waiting for the `RT_WLAN_EVT_READY` event. Register the callback function of the event that needs attention, execute the `rt_wlan_connect` wifi connection function, and return value will indicate whether the connection has been successful. If the WiFi connection succeeds, it needs to wait for the network to get the IP address before communication. Use the semaphore created in advance to wait for the network to be ready. Once the network is ready, it will be able to communicate.
After connecting to WIFI, wait for a while and then execute `rt_wlan_disconnect` to disconnect. The disconnect operation is blocked, and the return value indicates whether the disconnection was successful.
```c
#include <rthw.h>
#include <rtthread.h>
#include <wlan_mgnt.h>
#include <wlan_prot.h>
#include <wlan_cfg.h>
#define WLAN_SSID "SSID-A"
#define WLAN_PASSWORD "12345678"
#define NET_READY_TIME_OUT (rt_tick_from_millisecond(15 * 1000))
static rt_sem_t net_ready = RT_NULL;
static void
wifi_ready_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
rt_sem_release(net_ready);
}
static void
wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
{
rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
}
}
static void
wifi_disconnect_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
{
rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
}
}
static void
wifi_connect_fail_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
{
rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
}
}
rt_err_t wifi_connect(void)
{
rt_err_t result = RT_EOK;
/* Configuring WLAN device working mode */
rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION);
/* station connect */
rt_kprintf("start to connect ap ...\n");
net_ready = rt_sem_create("net_ready", 0, RT_IPC_FLAG_FIFO);
rt_wlan_register_event_handler(RT_WLAN_EVT_READY,
wifi_ready_callback, RT_NULL);
rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED,
wifi_connect_callback, RT_NULL);
rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED,
wifi_disconnect_callback, RT_NULL);
rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED_FAIL,
wifi_connect_fail_callback, RT_NULL);
/* connect wifi */
result = rt_wlan_connect(WLAN_SSID, WLAN_PASSWORD);
if (result == RT_EOK)
{
/* waiting for IP to be got successfully */
result = rt_sem_take(net_ready, NET_READY_TIME_OUT);
if (result == RT_EOK)
{
rt_kprintf("networking ready!\n");
}
else
{
rt_kprintf("wait ip got timeout!\n");
}
rt_wlan_unregister_event_handler(RT_WLAN_EVT_READY);
rt_sem_delete(net_ready);
rt_thread_delay(rt_tick_from_millisecond(5 * 1000));
rt_kprintf("wifi disconnect test!\n");
/* disconnect */
result = rt_wlan_disconnect();
if (result != RT_EOK)
{
rt_kprintf("disconnect failed\n");
return result;
}
rt_kprintf("disconnect success\n");
}
else
{
rt_kprintf("connect failed!\n");
}
return result;
}
int connect(int argc, char *argv[])
{
wifi_connect();
return 0;
}
MSH_CMD_EXPORT(connect, connect test.);
```
The results are as follows:
![Disconnection](figures/an0026_4.png)
### WiFi Auto Reconnection when Turn On
First enable the automatic reconnection function, use the command line to connect to the hotspot A, and connect another hotspot B. After waiting for a few seconds, power off hotspot B, the system will automatically retry connecting B hotspot. At this time, B hotspot connection can not be connected, and the system automatically switches hotspot A to connect. After the connection is successful, the system stops connecting.
```c
#include <rthw.h>
#include <rtthread.h>
#include <wlan_mgnt.h>
#include <wlan_prot.h>
#include <wlan_cfg.h>
static void
wifi_ready_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
}
static void
wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
{
rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
}
}
static void
wifi_disconnect_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
{
rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
}
}
static void
wifi_connect_fail_callback(int event, struct rt_wlan_buff *buff, void *parameter)
{
rt_kprintf("%s\n", __FUNCTION__);
if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
{
rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
}
}
int wifi_autoconnect(void)
{
/* Configuring WLAN device working mode */
rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION);
/* Start automatic connection */
rt_wlan_config_autoreconnect(RT_TRUE);
/* register event */
rt_wlan_register_event_handler(RT_WLAN_EVT_READY,
wifi_ready_callback, RT_NULL);
rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED,
wifi_connect_callback, RT_NULL);
rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED,
wifi_disconnect_callback, RT_NULL);
rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED_FAIL,
wifi_connect_fail_callback, RT_NULL);
return 0;
}
int auto_connect(int argc, char *argv[])
{
wifi_autoconnect();
return 0;
}
MSH_CMD_EXPORT(auto_connect, auto connect test.);
```
The results are as follows:
![Autoconnection](figures/an0026_5.png)

View File

@ -0,0 +1,314 @@
# Dynamic Module: dlmodule #
In traditional desktop operating systems, user space and kernel space are separate. The application runs in user space, and the kernel and kernel modules run in kernel space. The kernel module can be dynamically loaded and deleted to extend the kernel functionality. `dlmodule` is a software component of the dynamic module loading mechanism provided in kernel space of RT-Thread. In versions of RT-Thread v3.1.0, this was also called the `Application Module`. After RT-Thread v3.1.0 and later, it returned to the tradition and was named after the `dynamic module`.
`dlmodule` is more of an ELF format loader. The code segment and data segment of a separately compiled elf file are loaded into memory, and the symbols are parsed and bound to the API address exported by the kernel. The elf files are primarily placed on file systems under RT-Thread.
## Introduction ##
The dynamic module provides a mechanism for dynamically loading program modules for RT-Thread. Because it is also compiled independently of the kernel, it is more flexible to use. In terms of implementation, this is a mechanism to separate the kernel from the dynamic modules. Through this mechanism, the kernel and dynamic modules can be compiled separately, and at runtime, the compiled dynamic modules are loaded into the kernel through the module loader in the kernel.
In the dynamic module of RT-Thread, two formats are currently supported:
* `.mo` is an executable dynamic module that is suffixed with `.mo` when compiled; it can be loaded, and a main thread is automatically created in the system to execute `main()` function in this dynamic module; at the same time, `main(int argc, char**argv)` can also accept arguments on the command line.
* `.so` is a dynamic library compiled with `.so` as a suffix; it can be loaded and resided in memory, and some set of functions is used by other programs (code or dynamic modules in the kernel).
The current RT-Thread architecture supporting dynamic modules mainly includes ARM architecture and x86 architecture, and will be extended to MIPS and RISC-V architecture in the future. The RT-Thread kernel firmware section can use a variety of compiler toolchains, such as GCC, ARMCC, IAR and other toolchains; however, dynamic module partial compilation currently only supports GNU GCC toolchain compilation. Therefore, compiling the RT-Thread module requires downloading GCC tools, such as CodeSourcery's arm-none-eabi toolchain. In general, it's best to use kernel and dynamic modules to compile with the same toolchain (so that it doesn't produce inconsistent behavior in *libc*). In addition, dynamic modules can only be loaded into RAM and used for symbol resolution binding to the API address exported by the kernel. Instead of running directly in XIP mode based on Flash (because Flash can't modify the code segment again).
## Using Dynamic Module ##
When you want to use the dynamic modules in your system, you need to compile a firmware that supports dynamic modules, as well as dynamic modules that need to be run. The following two parts are compiling firmware and compiling dynamic modules.
### Compile Firmware ###
When you want to use the dynamic module, you need to open the corresponding option in the firmware configuration, use menuconfig to open the following configuration:
```c
RT-Thread Components --->
POSIX layer and C standard library --->
[*] Enable dynamic module with dlopen/dlsym/dlclose feature
```
Also open the configuration options of the file system:
```c
RT-Thread Components --->
Device virtual file system --->
[*] Using device virtual file system
```
The configuration parameters required for dynamic module compilation are set in *rtconfig.py* file corresponding to bsp:
```Python
M_CFLAGS = CFLAGS + '-mlong-calls -fPIC'
M_CXXFLAGS = CXXFLAGS + '-mlong-calls -fPIC'
M_LFLAGS = DEVICE + CXXFLAGS + '-Wl,--gc-sections,-z,max-page-size=0x4' +\
'-shared -fPIC -nostartfiles -nostdlib -static-libgcc'
M_POST_ACTION = STRIP + '-R .hash $TARGET\n' + SIZE + '$TARGET \n'
M_BIN_PATH = r'E:\qemu-dev310\fatdisk\root'
```
The relevant explanation is as follows:
* **M_CFLAGS**: C code compilation parameters used in dynamic module compilation, generally compiled here in PIC mode (that is, the code address supports floating mode execution);
- **M_CXXFLAGS**: C++ code compilation parameters used in dynamic module compilation, parameters similar to M_CFLAGS above;
- **M_LFLAGS**: Parameters when the dynamic module is linked. The same is the PIC method, and is linked in a shared library (partial link);
- **M_POST_ACTION**: the action to be performed after the dynamic module is compiled. Here, the elf file is stripped to reduce the size of the elf file.
- **M_BIN_PATH**: When the dynamic module is successfully compiled, whether the corresponding dynamic module file needs to be copied to a unified place;
Basically, these compilation configuration parameters for the ARM9, Cortex-A, and Cortex-M series are the same.
The kernel firmware also exports some function APIs to the dynamic module via `RTM(function)`. All exported symbols information in firmware can be listed by command A under MSH:
```
list_symbols
```
The `dlmodule` loader also parses the symbols that need to be parsed in the dynamic module according to the symbol table exported here to complete the final binding action.
This symbol table will be placed in a special section named `RTMSymTab`, so the corresponding firmware link script also needs to retain this area, and will not be removed by the linker optimization. You can add the corresponding information in the link script:
```text
/* section information for modules */
. = ALIGN(4);
__rtmsymtab_start = .;
KEEP(*(RTMSymTab))
__rtmsymtab_end = .;
```
Then execute the `scons` under the BSP project directory and generate the firmware without errors. Execute the command in the BSP project directory:
`scons --target=ua -s`
to generate the kernel header file search path and global macro definitions that need to be included when compiling the dynamic module.
### Compile Dynamic Module ###
There is a separate repository on github: [rtthread-apps](https://github.com/RT-Thread/rtthread-apps) , which contains some examples of dynamic modules and dynamic libraries.
Its directory structure is as follows:
| **Directory** | **Description** |
| --- | ---------------- |
| cxx | Demonstrates how to program in C++ using dynamic modules |
| hello | A "hello world" example |
| lib | Example of dynamic library |
| md5 | Generate md5 code for a file |
| tools | *Python/SConscript* scripts required for dynamic module compilation |
| ymodem | Download a file to the file system through the serial port using the YModem protocol |
You can clone this repository locally and compile it with the scons from the command line. For Windows platforms, the ENV tool is recommended.
After entering the console command line, enter the directory where the `rtthread-apps` repo is located (the same, please ensure that the full path of this directory does not contain spaces, Chinese characters, etc.), and set two variables:
* RTT_ROOT: points to the root directory of the RT-Thread code;
- BSP_ROOT: points to the project directory of the BSP;
Use follow commands on Windows in Env tool(assuming the BSP used is qemu-vexpress-a9):
```c
set RTT_ROOT=d:\your_rtthread
set BSP_ROOT=d:\your_rtthread\bsp\qemu-vexpress-a9
```
to set the corresponding environment variable. Then use the following command to compile the dynamic module, such as the hello example:
```
scons --app=hello
```
After compiling successfully, it will generate a `hello.mo` file in the `rtthread-apps/hello` directory.
You can also compile dynamic libraries, such as the lib example:
```
scons --lib=lib
```
After compiling successfully, it will generate the `lib.so` file in the `rtthread-apps/lib` directory.
We can put these `mo` and `so` files under the RT-Thread file system. Under msh, you can simply execute the `hello.mo` dynamic module as a `hello` command:
```c
msh />ls
Directory /:
hello.mo 1368
lib.so 1376
msh />hello
msh />Hello, world
```
After calling hello, the main function in `hello.mo` will be executed, and the corresponding dynamic module will be exited after execution. The code for `hello/main.c` is as follows:
```c
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello, world\n");
return 0;
}
```
## APIs of Dynamic Module
In addition to dynamically loading and executing dynamic modules via msh, dynamic modules can be loaded or unloaded using the dynamic module API provided by RT-Thread in the main program.
### Load Dynamic Module
```c
struct rt_dlmodule *dlmodule_load(const char* pgname);
```
|**Parameter**|**Description**|
| ---- | ---- |
| pgname | Dynamic module path |
|**Return**| —— |
| Dynamic module pointer | Successfully loaded |
| RT_NULL | Failed |
This function loads the dynamic module from the file system into memory, and if it is loaded correctly, returns a pointer to the module. This function does not create a thread to execute this dynamic module, just load the module into memory and parse the symbolic address.
### Execute Dynamic Module
```c
struct rt_dlmodule *dlmodule_exec(const char* pgname, const char* cmd, int cmd_size);
```
|**Parameter**|**Description**|
| ---- | ---- |
| pgname | Dynamic module path |
| cmd | Command line string including the dynamic module command itself |
| cmd_size | Command line string size |
|**Return**| —— |
| Dynamic module pointer | Run successfully |
| RT_NULL | Failed |
This function loads the dynamic module according to the `pgname` path and starts a thread to execute `main` of the dynamic module. At the same time, `cmd` is passed as the command line Parameter to `main` entry of the dynamic module.
### Exit Dynamic Module
```c
void dlmodule_exit(int ret_code);
```
|**Parameter**|**Description**|
| ---- | ---- |
| ret_code | Module's return parameter |
This function is called by the module runtime, it can set the return value of the module exit `ret_code`, and then exit from the module.
### Find Dynamic Modules
```c
struct rt_dlmodule *dlmodule_find(const char *name);
```
|**Parameter**|**Description**|
| ---- | ---- |
| name | Module name |
|**Return**| —— |
| Dynamic module pointer | Successful |
| RT_NULL | Failed |
This function uses `name` to find out if there is already a dynamic module loaded in the system.
### Return Dynamic Module
```c
struct rt_dlmodule *dlmodule_self(void);
```
|**Return**|**Description**|
| ---- | ---- |
| Dynamic module pointer | Successful |
| RT_NULL | Failed |
This function returns a pointer of the dynamic module in the calling context.
### Find Symbol
```c
rt_uint32_t dlmodule_symbol_find(const char *sym_str);
```
|**Parameter**|**Description**|
| ---- | ---- |
| sym_str | Symbol name |
|**Return**| —— |
| Symbol address | Successful |
| 0 | Failed |
This function returns the symbol address based on the symbol name.
## Libdl API of POSIX Standard ##
The POSIX standard libdl API is also supported in RT-Thread dlmodule. It is similar to loading a dynamic library into memory (and parsing some of the symbol information). This dynamic library provides the corresponding set of function operations. The libdl API needs to include the header files: `#include <dlfcn.h>`
### Open Dynamic Library
```c
void * dlopen (const char * pathname, int mode);
```
|**Parameter**|**Description**|
| ---- | ---- |
| pathname | Dynamic library path name |
| mode | The mode when opening a dynamic library, not used in RT-Thread |
|**Return**| —— |
| Dynamic library handle (`struct dlmodule` structure pointer) | Successful |
| NULL | Failed |
This function is similar to the `dlmodule_load` , which loads the dynamic library from the file system and returns the handle pointer of the dynamic library.
### Find Symbol
```c
void* dlsym(void *handle, const char *symbol);
```
|**Parameter**|**Description**|
| ---- | ---- |
| handle | Dynamic library handle, return value of `dlopen` |
| symbol | The symbol address to return |
|**Return**| —— |
| symbol address | Successful |
| NULL | Failed |
This function looks in the dynamic library `handle` for the presence of the symbol of `symbol` , if there is an address that returns it.
### Close Dynamic Library
```
int dlclose (void *handle);
```
|**Parameter**|**Description**|
| ---- | ---- |
| handle | Dynamic library handle |
|**Return**| —— |
| 0 | Successful |
| Negative number | Failed |
This function closes the dynamic library pointed to by `handle` and unloads it from memory. It should be noted that when the dynamic library is closed, the symbolic address originally returned by `dlsym` will no longer be available. If you still try to access it, it may cause a fault error.
## FAQs
Please refer to [*User Manual of Env*](../env/env.md) for issues related to the Env tool.
### Q: Dynamic modules cannot be run successfully according to the documentation.
**A:** Please update the RT-Thread source code to version 3.1.0 and above.
### Q: Compile the project with the scons command, prompting "undefined reference to __rtmsymtab_start".
**A:** Please refer to the qemu-vexpress-a9 BSP GCC link script file *link.lds* to add the following to the TEXT section of the project's GCC link script.
```
/* section information for modules */
. = ALIGN(4);
__rtmsymtab_start = .;
KEEP(*(RTMSymTab))
__rtmsymtab_end = .;
```

256
documentation/env/env.md vendored Normal file
View File

@ -0,0 +1,256 @@
# User Manual of Env
Env is a handy utility tool developed by RT-Thread team to build enviornment, graphical system configuration, and packages management for software projects that intend to run on RT-Thread operating system.
It is a wrapper tool for build-in menuconfig; an open source GUI tool which is designed to tailor for ease of use for developers. It can also be used to configure the kernel configuraiton parameters, components and software packages so that developers can construct the system like lego blocks.
## Main Features
- Menuconfig provides graphical interface to interact with operational logic and congfiguration parameters
- Each configuration option come with help session by default.
- It automates dependencies installation process.
- Automatically generate rtconfig.h without manual modification.
- It uses scons tool to streamline build project and compliation enviornment.
- Modular software packages and decoupling design make it easier to maintain.
- It also featured with point and click to download additional software packages and dependencies directly from Internet.
## Preparation
Env tool come with source code builder, compilation enviornment and package management system.
- [Download the Env tool]().
- Install Git (download link - https://git-scm.com/downloads). Follow git installation instructions and configure environment variable to add git.
- Take a note for working environment, all paths are not allowed to have Chinese characters or Spaces.
## User Guide of Env
### Open the control Console
The rt-thread software package environment is mainly based on the command line console so that the rt-thread development environment can be set up by minimizing the modification of configuration files.
There are two ways to open the console:
#### 1. click the Env directory executable file
To enter the Env directory, you can run `env.exe` in this directory. If it fails to open, you can try to use `env.bat`.
#### 2. open the Env console from the right-click menu in the folder
Add Env to the right-click menu:
![env settings](figures/Add_Env_To_Right-click_Menu-1.png)
![1561968527218](figures/Add_Env_To_Right-click_Menu-2.png)
![1561968626998](figures/Add_Env_To_Right-click_Menu-3.png)
Follow the steps from the image to launch the Env console from the right-click menu in any folder. Result are as follows:
![Right-click menu to launch the Env console](figures/console.png)
> Due to the need for the environment variables of the Env process to be set, the anti-virus software may misreport at the first startup. If the anti-virus software misreport is encountered, allow Env related programs to run, and then add related programs to the white list.
### Compile
Scons is a compile building tool used by RT-Thread to compile RT-Threads using the scons related commands.
#### Step One: Switch to the BSP root directory
If you use Method 1 to open Env console, you need to switch to the target BSP using the `cd` command.
For example, the target BSP is `rt-thread\bsp\stm32\stm32f103-dofly-lyc8`:
![stm32f429-apollo project directory](figures/cd_cmd.png)
#### Step Two: Compile the BSP
- Env carries `Python & scons` . To compile BSP, just use the default ARM_GCC toolchain by running `scons` command in the target BSP directory.
![compilation project using scons command](figures/use_scons.png)
Compiled successfully:
![complied successfully](figures/scons_done.png)
If you use mdk/iar for project development, you can use the project file in the BSP directly or use one of the following commands to regenerate the project and compile and download it.
```
scons --target=iar
scons --target=mdk4
scons --target=mdk5
```
For more scons tutorials, please refer to [*Scons*](../scons/scons.md).
### BSP configuration: menuconfig
Menuconfig is a graphical configuration tool that RT-Thread uses to configure and tailor the entire system.
#### Instruction for Shortcuts
Go to the BSP root directory and open the interface by entering `menuconfig`. The menuconfig common shortcuts are as shown:
![Commonly-used Shortcuts for menuconfig ](figures/hotkey.png)
#### Modify Settings
There are many types of configuration items in menuconfig, and the modification methods are different. The common types are:
- On/Off Type: Use the space bar to select or close
- Value, string type: After pressing the Enter key, a dialog box will appear, and the configuration items will be modified in the dialog box.
#### Save Settings
After selecting the configuration item, press `ESC` to exit, and select `Save` to automatically generate the `rtconfig.h` file. At this point, using the `scons` command again will recompile the project according to the new rtconfig.h file.
### Package Management
RT-Thread provides a package management platform where the officially available or developer-supplied packages are stored. The platform provides developers with a choice of reusable software packages that are an important part of RT-Thread.
[Click here](https://github.com/RT-Thread-packages) to view the official RT-Thread package, most of which have detailed documentation and usage examples.
As a part of Env, the `package` tool provides developers with management functions such as downloading, updating, and deleting packages.
Enter the `pkgs` command on the Env command line to see an introduction to the command:
```
> pkgs
usage: env.py package [-h] [--update] [--list] [--wizard] [--upgrade]
[--printenv]
optional arguments:
-h, --help show this help message and exit
--update update packages, install or remove the packages as you set in
menuconfig
--list list target packages
--wizard create a package with wizard
--upgrade update local packages list from git repo
--printenv print environmental variables to check
```
#### Download, update, and delete packages
Before downloading and updating the software package, you need to **open** the target package in `menuconfig`.
These packages locates in `RT-Thread online packages` , Once you enter the menu, you can see the following package categories:
![Package Categories](figures/menuconfig_packages_list.png)
Find the package you need and open, then save and exit menuconfig. The package will be marked, but has not been downloaded locally, so it is still unavailable.
- **download**: if the software package is selected but not downloaded, enter: `pkgs --update`, then the software package will be downloaded automatically;
- **update**: if the selected package has a latest update on the server and the version is selected **latest**, then enter `pkgs --update` , the package will be updated in local;
- **delete**: if a software package is not needed, deselect it in menuconfig and then use `pkgs --update` command. Then locally downloaded but unselected packages will be deleted.
#### Update local package information
As the package system grows, more and more packages will be added, so the list of packages in menuconfig may be **unsynchronized** with the server. This can be fixed by using `pkgs --upgrade` command, which not only synchronizes updates to local package information, but also upgrades to Env's functional scripts, which are recommended for regular use.
### Env Tool Configuration
- The new version of the Env tool includes an automatic update package and an option to automatically generate mdk/iar projects. The default is not enabled. It can be configured using `menuconfig -s/--setting` .
- Use `menuconfig -s` command to enter the Env configuration interface
![Env Configuration Interface](figures/menuconfig_s.png)
Press Enter to enter the configuration menu with 3 configuration options:
![configuration options](figures/menuconfig_s_auto_update.png)
The three options are:
- **Auto update pkgs config**Automatic package update function: After exiting the menuconfig function, `pkgs --update` command is automatically used to download and install the package and delete the old package. This feature is used when downloading online packages.
- **Auto create a MDK/IAR project**: After modifying the menuconfig configuration, you must re-generate the project by typing `scons --target=xxx` . Turning on this feature will automatically regenerate the project when you exit menuconfig, without having to manually enter the scons command to regenerate the project.
## Use Env in Your Project
### Requirements for Using Env
- Menuconfig is a feature of RT-Thread over version 3.0. It is recommended to update RT-Thread over version 3.0.
- Currently RT-Thread does not support `menuconfig` for all BSPs, which means that some BSPs can't be configured with menuconfig for the time being, but the commonly used BSPs are already supported.
### How to Modify Options in Menuconfig
If you want to add a macro definition in the configuration item of menuconfig, you can modify the `Kconfig` file under BSP. The modification method can search Kconfig syntax on the Internet for detailed documentation, or refer to the Kconfig file in RT-Thread or The Kconfig file in the BSP that supports menuconfig.
### To Add menuconfig function to New Project
New project here refers to a newly developed project that has not yet generated `.config` and `rtconfig.h`. Because these two files are only created when menuconfig is first saved. The specific process is as follows:
1. Copy the kconfig file from the BSP that already supports the menuconfig function to the new project root directory.
2. Note that modifying the RTT_ROOT value in Kconfig is the directory where RT-Thread is located, otherwise RTT_ROOT may be not found.
3. Start the configuration with the menuconfig command.
### To Add menuconfig function to Old Project
Old project here refers to the development that has been going on for a while, and there is a modified `rtconfig.h` file in the project, but there is no project configured with menuconfig. The specific process is as follows:
1. First back up the rtconfig.h file in the old project.
2. Use `scons --genconfig` command to generate a `.config` file from the existing `rtconfig.h` file. The `.config` file generated here saves the configuration parameters of the `rtconfig.h` file in the old project.
3. Copy the `kconfig` file from the BSP that already supports the menuconfig function to the root directory of the project you want to modify.
4. Note that modifying the RTT_ROOT value in Kconfig is the directory where RT-Thread is located, otherwise RTT_ROOT may be not found.
5. Use the menuconfig command to configure the old project we want to modify. Menuconfig will read the `.config` file generated in the second step and generate a new `.config` file and rtconfig.h file based on the configuration parameters of the old project.
6. Check the old and new rtconfig.h files. If there are any inconsistencies, you can use the menuconfig command to adjust the configuration items.
## Explore More with pip
In the Env environment, you can't directly use the pip tool provided by Python to install more modules. If you need to use the pip function in Env environment, you can reinstall the pip tool as follows:
1. Download the get-pip.py file from https://bootstrap.pypa.io/get-pip.py and save it on disk.
2. Run `python get-pip.py` command in the Env environment to reinstall the pip tool.
3. After the pip tool is reinstalled successfully, you can use `pip install module-name` command to install the required modules.
## Notes for Using Env
- For the first time, Env is recommended to go to the official website to download the latest version of the Env tool. The new version of Env will have better compatibility and also support automatic update commands.
- You can use the Env built-in command `pkgs --upgrade` to update the package list and Env's function code to minimize the problems you have fixed.
- Do not have Chinese or spaces in the routes of Env.
- Do not have Chinese or spaces in the routes where the BSP project is located.
## FAQs
### Q: There's unintelligible texts appear in Env.
**A:** First check if there is a Chinese route.
Check if the `chcp` command has been added to the system environment variable and try to change the character format to English using the `chcp 437` command. If the prompt does not have a `chcp` command, it is considered not to be added to the environment variable.
The directory where the `chcp` command is located may be added to the environment variable in the `system32` directory.
### Q: It prompts that the git command cannot be found.
  'git' is not recognized as an internal or external command, possible program or batch file.
**A:** Git is not installed. You need to install git and add environment variables.
### Q: It prompts that the CMD command cannot be found.
**A:** Right-click>> Property—>> Advanced System Settings—>> Environment Variable, Add `C:\Windows\System32` to system environment variables.
### Q: Prompt "no module named site" when running python.
**A:** Computer right button >> Properties—>> Advanced System Settings—>> Environment Variable, in the user variable of the administrator, create a new variable named PYTHONHOME, and the variable value is: `F:\git_repositories\env\tools\Python27` the installation route of Python in Env, do not add ";" afterwards, otherwise it will be invalid. If add PYTHONHOME can not solve theproblem, try to add PYTHONPATH in the same way.
### Q: What types of projects can I generate under Env?
**A:**
1. Currently, you can use the scons tool to generate mdk/iar projects under Env. There is no automatic generation of eclipse projects.
2. Generally, using gcc's toolchain, using an editor such as source insight or VS Code to edit the code and compile with `scons` command.
### Q:How can my own BSP support menuconfig?
**A:** You can refer to this chapter **Use Env in Your Project**.
### Q: What is the difference between the pkgs --upgrade command and the pkgs --update command?
**A:**
1. The `pkgs --upgrade` command is used to upgrade the Env script itself and the list of packages. You cannot select a recently updated package without the latest package list.
2. The `pkgs --update` command is used to update the package itself. For example, if you selected json and mqtt packages in menuconfig, you did not download them when you exit menuconfig. You need to use the `pkgs --update` command, at which point Env will download the package you selected and add it to your project.
3. The new version of Env supports the `menuconfig -s/--setting` command. If you don't want to use the `pkgs --update` command after replacing the package, configure Env after using the `menuconfig -s/--setting` command. Select each time you use menuconfig. After the package is automatically updated.
### Q: Prompt “can't find file Kconfig” while using menuconfig.
**A:** The Kconfig file is missing from the current working BSP directory. Please refer *To Add menuconfig function to New Project* and *To Add menuconfig function to Old Project*.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
documentation/env/figures/cd_cmd.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
documentation/env/figures/console.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
documentation/env/figures/hotkey.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
documentation/env/figures/q1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
documentation/env/figures/scons_done.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
documentation/env/figures/use_scons.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,562 @@
# FinSH Console
In the early days of computer development, before the advent of graphics systems, there was no mouse or even a keyboard. How did people interact with computers at the time? The earliest computers used a punched note to enter commands into the computer and write the program. Later, with the continuous development of computers, monitors and keyboards became the standard configuration of computers, but the operating system at this time did not support the graphical interface. Computer pioneers developed a software that accepts commands entered by the user, and after interpretation, passes it to The operating system and return the results of the operating system execution to the user. This program wraps around the operating system like a layer of shell, so it's called a shell.
Embedded devices usually need to connect the development board to the PC for communication. Common connections include: serial port, USB, Ethernet, Wi-Fi, etc. A flexible shell should also support working on multiple connection methods. With the shell, the developer can easily get the system running and control the operation of the system through commands. Especially in the debugging phase, with the shell, in addition to being able to locate the problem more quickly, the developer can also use the shell to call the test function, change the parameters of the test function, reduce the number of times the code is downloaded, and shorten the development time of the project.
FinSH is the command line component (shell) of RT-Thread. It is based on the above considerations. FinSH is pronounced [ˈfɪnʃ]. After reading this chapter, we will have a deeper understanding of how FinSH works and how to export your own commands to FinSH.
## Introduction of FinSH
FinSH is the command line component of RT-Thread. It provides a set of operation interfaces for users to call from the command line. It is mainly used to debug or view system information. It can communicate with a PC using serial/Ethernet/USB, etc. The hardware topology is shown below:
![FinSH Hardware connection diagram](figures/finsh-hd.png)
The user inputs a command in the control terminal, and the control terminal transmits the command to the FinSH in the device through the serial port, USB, network, etc., FinSH will read the device input command, parse and automatically scan the internal function table, find the corresponding function name, and execute the function. The response is output, the response is returned through the original path, and the result is displayed on the control terminal.
When using a serial port to connect a device to a control terminal, the execution flow of the FinSH command is as follows:
![FinSH FinSH Command execution flow chart](figures/finsh-run.png)
FinSH supports the rights verification function. After the system is started, the system will perform the rights verification. Only when the rights verification is passed, the FinSH function will be enabled. This improves the security of system input.
FinSH supports auto-completion, and viewing history commands, etc. These functions can be easily accessed through the keys on the keyboard. The keys supported by FinSH are shown in the following table:
|Keys| **Functional Description** |
|----------|--------------|
| Tab key | Pressing the Tab key when no characters are entered will print all commands supported by the current system. If you press the Tab key when you have entered some characters, it will find the matching command, and will also complete the file name according to the file system's current directory, and you can continue to input, multiple completions. |
| ↑↓ key | Scroll up and down the recently entered history command |
| Backspace key | Delete character |
| ←→ key | Move the cursor left or right |
FinSH supports two input modes, the traditional command line mode and the C language interpreter mode.
### Traditional Command Line Mode
This mode is also known as msh(module shell). In msh mode, FinSH is implemented in the same way as the traditional shell (dos/bash). For example, you can switch directories to the root directory with the `cd /` command.
MSH can parse commands into parameters and parameters separated by spaces. Its command execution format is as follows:
```
command [arg1] [arg2] [...]
```
The command can be either a built-in command in RT-Thread or an executable file.
### C Language Interpreter Mode
This mode is also known as C-Style mode. In C language interpreter mode, FinSH can solve and parse most C language expressions, and use functions like C to access functions and global variables in the system. In addition, it can create variables through the command line. In this mode, the command entered must be similar to the function call in C language, that is, you must carry the `()` symbol. For example, to output all current threads and their status in the system, type `list_thread()` in FinSH to print out the required information. The output of the FinSH command is the return value of this function. For some functions that do not have a return value (void return value), this printout has no meaning.
Initially FinSH only supported C-Style mode. Later, with the development of RT-Thread, C-Style mode is not convenient when running scripts or programs, and it is more convenient to use traditional shell method. In addition, in C-Style mode, FinSH takes up a lot of volume. For these reasons, the msh mode has been added to RT-Thread. The msh mode is small and easy to use. It is recommended that you use the msh mode.
If both modes are enabled in the RT-Thread, they can be dynamically switched. Enter the `exit` in msh mode and press `Enter` to switch to C-Style mode. Enter `msh()` in C-Style mode and press `Enter` to enter msh mode. The commands of the two modes are not common, and the msh command cannot be used in C-Style mode, and vice versa.
## FinSH Built-in Commands
Some FinSH commands are built in by default in RT-Thread. You can print all commands supported by the current system by entering help in FinSH and pressing Enter or directly pressing Tab. The built-in commands in C-Style and msh mode are basically the same, so msh is taken as an example here.
In msh mode, you can list all currently supported commands by pressing the Tab key. The number of default commands is not fixed, and the various components of RT-Thread will output some commands to FinSH. For example, when the DFS component is opened, commands such as `ls`, `cp`, and `cd` are added to FinSH for developers to debug.
The following are all currently supported commands that display RT-Thread kernel status information after pressing the Tab key. The command name is on the left and the description of the command on the right:
```c
RT-Thread shell commands:
version - show RT-Thread version information
list_thread - list thread
list_sem - list semaphore in system
list_event - list event in system
list_mutex - list mutex in system
list_mailbox - list mail box in system
list_msgqueue - list message queue in system
list_timer - list timer in system
list_device - list device in system
exit - return to RT-Thread shell mode.
help - RT-Thread shell help.
ps - List threads in the system.
time - Execute command with time.
free - Show the memory usage in the system.
```
Here lists the field information returned after entering the common commands, so that the developer can understand the content of the returned information.
### Display Thread Status
Use the `ps` or `list_thread` command to list all thread information in the system, including thread priority, state, maximum stack usage, and more.
```c
msh />list_thread
thread pri status sp stack size max used left tick error
-------- --- ------- ---------- ---------- ------ ---------- ---
tshell 20 ready 0x00000118 0x00001000 29% 0x00000009 000
tidle 31 ready 0x0000005c 0x00000200 28% 0x00000005 000
timer 4 suspend 0x00000078 0x00000400 11% 0x00000009 000
```
list_thread Return field description:
|**Field** |**Description** |
|------------|----------------------------|
| thread | Thread name |
| pri | Thread priority |
| status | The current state of the thread |
| sp | The current stack position of the thread |
| stack size | Thread stack size |
| max used | The maximum stack position used in thread history |
| left tick | The number of remaining ticks of the thread |
| error | Thread error code |
### Display Semaphore Status
Use the `list_sem` command to display all semaphore information in the system, including the name of the semaphore, the value of the semaphore, and the number of threads waiting for this semaphore.
```c
msh />list_sem
semaphore v suspend thread
-------- --- --------------
shrx 000 0
e0 000 0
```
list_sem Return field description:
|**Field** | **Description** |
|----------------|--------------------------|
| semaphore | Semaphore name |
| v | The current value of semaphore |
| suspend thread | The number of threads waiting for this semaphore |
### Display Event Status
Use the `list_event` command to display all event information in the system, including the event name, the value of the event, and the number of threads waiting for this event.
```c
msh />list_event
event set suspend thread
----- ---------- --------------
```
list_event Return field description:
| Field | **Description** |
|----------------|----------------------------------|
| event | Event set name |
| set | The current event in the event set |
| suspend thread | The number of threads waiting for an event in this event set |
### Display Mutex Status
Use the `list_mutex` command to display all mutex information in the system, including the mutex name, the owner of the mutex, and the number of nestings the owner holds on the mutex.
```c
msh />list_mutex
mutex owner hold suspend thread
-------- -------- ---- --------------
fat0 (NULL) 0000 0
sal_lock (NULL) 0000 0
```
list_mutex Return field description:
| **Field** | **Description** |
|----------------|------------------------------------|
| mutxe | Mutex name |
| owner | The thread currently holding the mutex |
| hold | The number of times the holder is nested on this mutex |
| suspend thread | The number of threads waiting for this mutex |
### Display Mailbox Status
Use the `list_mailbox` command to display all mailbox information in the system, including the mailbox name, the number of messages in the mailbox, and the maximum number of messages the mailbox can hold.
```c
msh />list_mailbox
mailbox entry size suspend thread
-------- ---- ---- --------------
etxmb 0000 0008 1:etx
erxmb 0000 0008 1:erx
```
list_mailbox Return field description:
| Field | **Description** |
|----------------|----------------------------|
| mailbox | Mailbox name |
| entry | The number of messages included in the mailbox |
| size | The maximum number of messages a mailbox can hold |
| suspend thread | The number of threads waiting for this mailbox |
### Display Message Queue Status
Use the `list_msgqueue` command to display all message queue information in the system, including the name of the message queue, the number of messages it contains, and the number of threads waiting for this message queue.
```c
msh />list_msgqueue
msgqueue entry suspend thread
-------- ---- --------------
```
list_msgqueue Return field description:
| Field | **Description** |
|----------------|----------------------------|
| msgqueue | Message queue name |
| entry | The number of messages currently included in the message queue |
| suspend thread | Number of threads waiting for this message queue |
### Display Memory Pool Status
Use the `list_mempool` command to display all the memory pool information in the system, including the name of the memory pool, the size of the memory pool, and the maximum memory size used.
```c
msh />list_mempool
mempool block total free suspend thread
------- ---- ---- ---- --------------
signal 0012 0032 0032 0
```
list_mempool Return field description:
| Field | **Description** |
|----------------|--------------------|
| mempool | Memory pool name |
| block | Memory block size |
| total | Total memory block |
| free | Free memory block |
| suspend thread | The number of threads waiting for this memory pool |
### Display Timer Status
Use the `list_timer` command to display all the timer information in the system, including the name of the timer, whether it is the periodic timer, and the number of beats of the timer timeout.
```c
msh />list_timer
timer periodic timeout flag
-------- ---------- ---------- -----------
tshell 0x00000000 0x00000000 deactivated
tidle 0x00000000 0x00000000 deactivated
timer 0x00000000 0x00000000 deactivated
```
list_timer Return field description:
| Field | **Description** |
|----------|--------------------------------|
| timer | Timer name |
| periodic | Whether the timer is periodic |
| timeout | The number of beats when the timer expires |
| flag | The state of the timer, activated indicates active, and deactivated indicates inactive |
### Display Device Status
Use the `list_device` command to display all device information in the system, including the device name, device type, and the number of times the device was opened.
```c
msh />list_device
device type ref count
------ ----------------- ----------
e0 Network Interface 0
uart0 Character Device 2
```
list_device Return field description:
| Field | Description |
|-----------|----------------|
| device | Device name |
| type | Device type |
| ref count | The number of times the device was opened |
### Display Dynamic Memory Status
Use the `free` command to display all memory information in the system.
```c
msh />free
total memory: 7669836
used memory : 15240
maximum allocated memory: 18520
```
free Return field description:
| Field | Description |
|--------------------------|------------------|
| total memory | Total memory size |
| used memory | Used memory size |
| maximum allocated memory | Maximum allocated memory |
## Custom FinSH Command
In addition to the commands that come with FinSH, FinSH also provides multiple macro interfaces to export custom commands. The exported commands can be executed directly in FinSH.
### Custom msh Command
The custom msh command can be run in msh mode. To export a command to msh mode, you can use the following macro interface
```
MSH_CMD_EXPORT(name, desc);
```
|**Parameter**|**Description** |
|----------|----------------|
| name | The command to export |
| desc | Description of the export command |
This command can export commands with parameters, or export commands without parameters. When exporting a parameterless command, the input parameter of the function is void. The example is as follows
```c
void hello(void)
{
rt_kprintf("hello RT-Thread!\n");
}
MSH_CMD_EXPORT(hello , say hello to RT-Thread);
```
When exporting a command with parameters, the function's input parameters are `int argc` and `char**argv`. Argc represents the number of arguments, and argv represents a pointer to a command-line argument string pointer array. An example of exporting a parameter command is as follows:
```c
static void atcmd(int argc, char**argv)
{
……
}
MSH_CMD_EXPORT(atcmd, atcmd sample: atcmd <server|client>);
```
### Custom C-Style Commands and Variables
Export custom commands to C-Style mode can use the following interface
```
FINSH_FUNCTION_EXPORT(name, desc);
```
|**Parameter**| **Description** |
|----------|----------------|
| name | The command to export |
| desc | Description of the export command |
The following example defines a `hello` function and exports it as a command in C-Style mode
```c
void hello(void)
{
rt_kprintf("hello RT-Thread!\n");
}
FINSH_FUNCTION_EXPORT(hello , say hello to RT-Thread);
```
In a similar way, you can also export a variable that can be accessed through the following interface
```
FINSH_VAR_EXPORT(name, type, desc);
```
| Parameter | **Description** |
|----------|----------------|
| name | The variable to be exported |
| type | Type of variable |
| desc | Description of the exported variable |
The following example defines a `dummy` variable and exports it to a variable command in C-Style mode.
```c
static int dummy = 0;
FINSH_VAR_EXPORT(dummy, finsh_type_int, dummy variable for finsh)
```
### Custom Command Rename
The function name length of FinSH is limited. It is controlled by the macro definition `FINSH_NAME_MAX` in `finsh.h`. The default is 16 bytes, which means that the FinSH command will not exceed 16 bytes in length. There is a potential problem here: when a function name is longer than FINSH_NAME_MAX, after using FINSH_FUNCTION_EXPORT to export the function to the command table, the full function name is seen in the FinSH symbol table, but a full node execution will result in a *null node* error. This is because although the full function name is displayed, in fact FinSH saves the first 16 bytes as a command. Too many inputs will result in the command not being found correctly. In this case, you can use `FINSH_FUNCTION_EXPORT_ALIAS` to re-export the command name.
```
FINSH_FUNCTION_EXPORT_ALIAS(name, alias, desc);
```
| Parameter | Description |
|----------|-------------------------|
| name | The command to export |
| alias | The name that is displayed when exporting to FinSH |
| desc | Description of the export command |
The command can be exported to msh mode by adding `__cmd_` to the renamed command name. Otherwise, the command will be exported to C-Style mode. The following example defines a `hello` function and renames it to `ho` and exports it to a command in C-Style mode.
```c
void hello(void)
{
rt_kprintf("hello RT-Thread!\n");
}
FINSH_FUNCTION_EXPORT_ALIAS(hello , ho, say hello to RT-Thread);
```
## FinSH Function Configuration
The FinSH function can be cropped, and the macro configuration options are defined in the rtconfig.h file. The specific configuration items are shown in the following table.
| **Macro Definition** | **Value Type** | Description | Default |
|------------------------|----|------------|-------|
| #define RT_USING_FINSH | None | Enable FinSH | on |
| #define FINSH_THREAD_NAME | String | FinSH thread name | "tshell" |
| #define FINSH_USING_HISTORY | None | Turn on historical traceback | on |
| #define FINSH_HISTORY_LINES | Integer type | Number of historical command lines that can be traced back | 5|
| #define FINSH_USING_SYMTAB | None | Symbol table can be used in FinSH | on |
|#define FINSH_USING_DESCRIPTION | None | Add a description to each FinSH symbol | on |
| #define FINSH_USING_MSH| None | Enable msh mode | on |
| #define FINSH_USING_MSH_ONLY | None | Use only msh mode | on |
| #define FINSH_ARG_MAX | Integer type | Maximum number of input parameters | 10 |
| #define FINSH_USING_AUTH | None | Enable permission verification | off |
| #define FINSH_DEFAULT_PASSWORD | String | Authority verification password | off |
The reference configuration example in rtconfig.h is as follows, and can be configured according to actual functional requirements.
```c
/* Open FinSH */
#define RT_USING_FINSH
/* Define the thread name as tshell */
#define FINSH_THREAD_NAME "tshell"
/* Open history command */
#define FINSH_USING_HISTORY
/* Record 5 lines of history commands */
#define FINSH_HISTORY_LINES 5
/* Enable the use of the Tab key */
#define FINSH_USING_SYMTAB
/* Turn on description */
#define FINSH_USING_DESCRIPTION
/* Define FinSH thread priority to 20 */
#define FINSH_THREAD_PRIORITY 20
/* Define the stack size of the FinSH thread to be 4KB */
#define FINSH_THREAD_STACK_SIZE 4096
/* Define the command character length to 80 bytes */
#define FINSH_CMD_SIZE 80
/* Open msh function */
#define FINSH_USING_MSH
/* Use msh function by default */
#define FINSH_USING_MSH_DEFAULT
/* The maximum number of input parameters is 10 */
#define FINSH_ARG_MAX 10
```
## FinSH Application Examples
### Examples of msh Command without Arguments
This section demonstrates how to export a custom command to msh. The sample code is as follows, the hello function is created in the code, and the `hello` function can be exported to the FinSH command list via the `MSH_CMD_EXPORT` command.
```c
#include <rtthread.h>
void hello(void)
{
rt_kprintf("hello RT-Thread!\n");
}
MSH_CMD_EXPORT(hello , say hello to RT-Thread);
```
Once the system is up and running, press the tab key in the FinSH console to see the exported command:
```c
msh />
RT-Thread shell commands:
hello - say hello to RT-Thread
version - show RT-Thread version information
list_thread - list thread
……
```
Run the `hello` command and the results are as follows:
```c
msh />hello
hello RT_Thread!
msh />
```
### Example of msh Command with Parameters
This section demonstrates how to export a custom command with parameters to FinSH. The sample code is as follows, the `atcmd()` function is created in the code, and the `atcmd()` function can be exported to the msh command list via the MSH_CMD_EXPORT command.
```c
#include <rtthread.h>
static void atcmd(int argc, char**argv)
{
if (argc < 2)
{
rt_kprintf("Please input'atcmd <server|client>'\n");
return;
}
if (!rt_strcmp(argv[1], "server"))
{
rt_kprintf("AT server!\n");
}
else if (!rt_strcmp(argv[1], "client"))
{
rt_kprintf("AT client!\n");
}
else
{
rt_kprintf("Please input'atcmd <server|client>'\n");
}
}
MSH_CMD_EXPORT(atcmd, atcmd sample: atcmd <server|client>);
```
Once the system is running, press the Tab key in the FinSH console to see the exported command:
```c
msh />
RT-Thread shell commands:
hello - say hello to RT-Thread
atcmd - atcmd sample: atcmd <server|client>
version - show RT-Thread version information
list_thread - list thread
……
```
Run the `atcmd` command and the result is as follows:
```c
msh />atcmd
Please input 'atcmd <server|client>'
msh />
```
Run the `atcmd server` command and the result is as follows:
```c
msh />atcmd server
AT server!
msh />
```
Run the `atcmd client` command and the result is as follows:
```c
msh />atcmd client
AT client!
msh />
```
## FinSH Porting
FinSH is written entirely in ANSI C and has excellent portability; it has a small memory footprint, and FinSH will not dynamically request memory if you do not use the functions described in the previous section to dynamically add symbols to FinSH. The FinSH source is located in the `components/finsh` directory. Porting FinSH requires attention to the following aspects:
* FinSH thread
Each command execution is done in the context of a FinSH thread (that is, a tshell thread). When the RT_USING_FINSH macro is defined, the FinSH thread can be initialized by calling `finsh_system_init()` in the initialization thread. In RT-Thread 1.2.0 and later, you don't have to use the `finsh_set_device(const char* device_name)` function to explicitly specify the device to be used. Instead, the `rt_console_get_device()` function is called automatically to use the console device (The `finsh_set_device(const char* device_name)` must be used in 1.1.x and below to specify the device used by FinSH. The FinSH thread is created in the function `finsh_system_init()` function, which will wait for the rx_sem semaphore.
* FinSH output
The output of FinSH depends on the output of the system and relies on the `rt_kprintf()` output in RT-Thread. In the startup function `rt_hw_board_init()`, the `rt_console_set_device(const char* name)` function sets the FinSH printout device.
* FinSH input
After the rin_sem semaphore is obtained, the FinSH thread calls the `rt_device_read()` function to obtain a character from the device (select serial device) and then process it. So the migration of FinSH requires the implementation of the `rt_device_read()` function. The release of the rx_sem semaphore completes the input notification to the FinSH thread by calling the `rx_indicate()` function. The usual process is that when the serial port receive interrupt occurs (that is, the serial port has input character), the interrupt service routine calls the `rx_indicate()` function to notify the FinSH thread that there is input, and then the FinSH thread obtains the serial port input and finally performs the corresponding command processing.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,568 @@
Interrupt Management
==============
Interrupts often occur in embedded operating systems. When the CPU is processing a normal task, an external urgent event has occurred, requiring the CPU to suspend the current task to handle the asynchronous event. After the external event has been handled, CPU then returns to the interrupted address to continue working on the previous task. The system that implements this function is called the interrupt system, and the source of the request requesting for the CPU interrupt is called the interrupt source. An interrupt is an exception. An exception is any event that causes the processor to move away from normal operation and execute special code. If it is not processed in time, the system will either encounter an error or face a complete breakdown. So appropriately handling exceptions to avoid errors is a very important part of improving software robustness (stability). The following picture is a simple interrupt diagram.
![Interrupt Diagram](figures/09interrupt_work.png)
Interrupt processing is closely related to the CPU architecture. Therefore, this chapter first introduces the ARM Cortex-M CPU architecture, and then introduces the RT-Thread interrupt management mechanism in conjunction with the Cortex-M CPU architecture. After reading this chapter, you will learn more about the interrupt handling process of RT-Thread, how to add an interrupt service routine (ISR) and other matters related.
Cortex-M CPU Architecture Foundation
--------------------
Unlike older classic ARM processors (like ARM7, ARM9), the ARM Cortex-M processor has a very different architecture. Cortex-M is serious which Cortex M0/M3/M4/M7 models. There will be some differences between each model. For example, the Cortex-M4 has more floating point calculation functions than the Cortex-M3, but their programming models are basically the same, so the parts of the book that describe interrupt management and porting are not going to be too finely differentiated for the Cortex M0/M3/M4/M7. This section focuses on the architectural aspects related to RT-Thread interrupt management.
### Introduction to Register
The register set of Cortex-M series CPU has 16 general register sets and several special function registers from R0~R15, as shown in the figure below.
R13 in the general register set is used as the stack pointer register (SP); R14 is used as the link register (LR), which is used to store the return address when the subroutine is called; R15 is used as the program counter (PC) , where the stack pointer register can be either the main stack pointer (MSP) or the process stack pointer (PSP).
![Register Schematic](figures/09interrupt_table.png)
Special function registers include program status word register bank (PSRs), interrupt mask register banks (PRIMASK, FAULTMASK, BASEPRI), and control registers (CONTROL). Special function registers can be accessed through MSR/MRS instructions, such as:
```
MRS R0, CONTROL ; Read CONTROL to R0
MSR CONTROL, R0 ; Write R0 to the CONTROL register
```
The program status word registers stores arithmetic and logic flags, such as negative flags, null result flags, overflow flags, and so on. The interrupt mask register bank controls the Cortex-M interrupt disable. The control registers are used to define the privilege level and decide which stack pointer is to be used.
In the case of a Cortex-M4 or Cortex-M7 with a floating point unit, the control register is also used to indicate whether the floating point unit is currently in use. The floating point unit contains 32 floating point general-purpose registers S0~S31 and a special FPSCR register (Floating point status and control register).
### Operating  Scheme and Privilege Level
Cortex-M introduces the concept of operation scheme and privilege level, which are thread mode and processing mode respectively. If it enters exception or interrupt processing, it enters processing mode, otherwise it is thread mode.
![Cortex-M Working Mode Switching Diagram](figures/09interrupt_work_sta.png)
Cortex-M has two running levels, privilege-level and user-level. Thread mode can work at both privilege-level and user-level, while processing mode always works at the privilege-level and can be controlled by the CONTROL special register. The switching of different working modes is as shown in the figure above.
Cortex-M's stack register SP corresponds to two physical registers MSP and PSP, MSP is the main stack, PSP is the process stack. Processing mode always uses MSP as the stack; thread mode can choose to use MSP or PSP as the stack, also controlled through special register CONTROL. After reset, Cortex-M enters thread mode, privilege-level, and uses the MSP stack by default.
### Nested Vector Interrupt Controller
The Cortex-M interrupt controller is called NVIC (nested vectored interrupt controller) and supports interrupt nesting. When an interrupt is triggered and the system responds, the processor hardware automatically pushes the context register of the current location of running into the interrupt stack. The registers in this section include the PSR, PC, LR, R12, and R3-R0 registers.
![Relationship between Cortex-M Kernel and NVIC Diagram](figures/09relation.png)
When the system is servicing an interrupt, if a higher priority interrupt is triggered, then the processor will also interrupt the currently running interrupt service routine, and then save the context of the interrupt service program register PSR, PC, LR, R12, R3-R0 to the interrupt stack.
### PendSV System Call
PendSV, also known as a suspendable system call, is an exception that can be suspended like a normal interrupt. It is specifically designed to assist the operating system in context switching. PendSV exceptions are initialized as lowest priority exceptions. Each time a context switch is required, the PendSV exception is triggered manually, and the context switch is performed in the PendSV exception handler. The detailed process of operating system context switching using the PendSV mechanism will be illustrated in the next chapter, *Kernel Porting*.
RT-Thread Interruption Mechanism
---------------------
### Interrupt Vector Table
The interrupt vector table is the entry point for all interrupt handlers. The following figure shows the Cortex-M serious of interrupt handlers: linking a function (user interrupt service routine) to the interrupt vector in a virtual interrupt vector table. When the interrupt vector corresponds to an interrupt, the hooked user interrupt service routine is called.
![Interrupt Processing](figures/09interrupt_handle.png)
On the Cortex-M core, all interrupts are processed using the interrupt vector table which means when an interrupt is triggered, the processor will directly determine which interrupt source it is, and then jump directly to the corresponding fixed location for processing. The interrupt service routines must be placed together at a uniform address (this address must be set to the NVIC interrupt vector offset register). The interrupt vector table is generally defined by an array or given in the start code. Given by the start code is applied by default:
```c
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset processing function
DCD NMI_Handler ; NMI processing function
DCD HardFault_Handler ; Hard Fault processing function
DCD MemManage_Handler ; MPU Fault processing function
DCD BusFault_Handler ; Bus Fault processing function
DCD UsageFault_Handler ; Usage Fault processing function
DCD 0 ; reserve
DCD 0 ; reserve
DCD 0 ; reserve
DCD 0 ; reserved
DCD SVC_Handler ; SVCall processing function
DCD DebugMon_Handler ; Debug Monitor processing function
DCD 0 ; reserve
DCD PendSV_Handler ; PendSV processing function
DCD SysTick_Handler ; SysTick processing function
… …
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
… …
```
Note the [WEAK] after the code, which is the symbol weakening identifier. The symbols before [WEAK] (such as NMI_Handler, HardFault_Handler) will be weakened if the entire code encounters symbols with the same names (for example, NMI_Handler function with the same name), then the code will use symbols that are not weakened (functions with the same name as NMI_Handler), and the code associated with the weakened symbols will be automatically discarded.
Take the SysTick interrupt as an example. In the system startup code, you need to fill in the SysTick_Handler interrupt entry function, and then implement the function to respond to the SysTick interrupt. The interrupt handler function sample program is as follows:
```c
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
```
### Interrupt Processing
In RT-Thread interrupt management, interrupt handler is divided into three parts: interrupt preamble, user interrupt service routine, and interrupt follow-up procedure, as shown in the following figure:
![3 Parts of the Interrupt Handler](figures/09interrupt_work_process.png)
#### Interrupt Preamble
The main job of interrupt preamble is as follows:
1) Save the CPU interrupt context. This part is related to the CPU architecture. Different CPU architectures are implemented differently.
For Cortex-M, this part of work is done automatically by hardware. When an interrupt is triggered and the system responds, the processor hardware automatically pushes the context register of the currently running portion into the interrupt stack. The registers in this section include the PSR, PC, LR, R12, and R3-R0 registers.
2) Inform the kernel to enter the interrupt state, call the rt_interrupt_enter() function, and add 1 to the global variable rt_interrupt_nest to record the number of levels of interrupt nesting. The code is as follows.
```c
void rt_interrupt_enter(void)
{
rt_base_t level;
level = rt_hw_interrupt_disable();
rt_interrupt_nest ++;
rt_hw_interrupt_enable(level);
}
```
#### User Interrupt Service Routine
In the user interrupt service routine (ISR), there are two cases. The first case is that no thread switching is performed. In this case, after user interrupt service routine and interrupt subsequent program finished running, it exits and return to the interrupted thread. .
In another case, thread switching is required during interrupt processing. In this case, the rt_hw_context_switch_interrupt() function is called for context switching. This function is related to the CPU architecture, and different CPU architectures are implemented differently.
In Cortex-M architecture, the function implementation of rt_hw_context_switch_interrupt() is shown in the following figure. It sets the thread rt_interrupt_to_thread variable that needs to be switched, and then triggers the PendSV exception (PendSV exception is specifically used to assist context switching and is initialized to the lowest level). After the PendSV exception is triggered, the PendSV exception interrupt handler will not be executed immediately as the interrupt processing is still in progress, the PendSV exception interrupt handler will be entered only after the interrupt subsequent program finishes running and exited the interrupt processing.
![Function rt_hw_context_switch_interrupt() Implementation Process](figures/09fun1.png)
#### Interrupt Follow-up Procedure
The main work done by interrupt follow-up procedure is:
1 Inform the kernel to leave the interrupt state and reduce the global variable rt_interrupt_nest by 1 through calling the rt_interrupt_leave() function. The code is as follows.
```c
void rt_interrupt_leave(void)
{
rt_base_t level;
level = rt_hw_interrupt_disable();
rt_interrupt_nest --;
rt_hw_interrupt_enable(level);
}
```
2 Restore the CPU context before the interrupt. If thread is not switched during the interrupt processing, the CPU context of the *from* thread is restored. If the thread is switched during the interrupt, the CPU context of the *to* thread is restored. This part of the implementation is related to the CPU architecture. Different CPU architectures are implemented differently. The implementation process in the Cortex-M architecture is shown in the following figure.
![Function rt_hw_context_switch_interrupt() Implementation Process](figures/09fun2.png)
### Interrupt Nesting
In the case of interrupt nesting, in the process of executing the interrupt service routine, if a high priority interrupt occurs, the execution of the current interrupt service routine will be interrupted to execute the interrupt service routine of the high priority interrupt. After the processing of the high priority interrupt is completed, the interrupted interrupt service routine is resumed. If thread scheduling is required, the thread context switch will occur when all interrupt handlers finish running, as shown in the following figure.
![Thread Switching during Interrupt](figures/09ths_switch.png)
### Interrupt Stack
During the interrupt processing, before the system responds to the interrupt, the software code (or processor) needs to save the context of the current thread (usually stored in the thread stack of the current thread), and then call the interrupt service routine for interrupt response and processing. During interrupt processing (essentially calling the user's interrupt service routine function), the interrupt handler function is likely to have its own local variables, which require the corresponding stack space to save, so the interrupt response still needs a stack space as the context to run the interrupt handler. The interrupt stack can be saved in the stack of the interrupted thread. When exiting from the interrupt, the corresponding thread is resumed to be executed.
The interrupt stack can also be completely separated from the thread stack, that is, when entering the interrupt each time, after the interrupt thread context is saved, it switches to the new interrupt stack and runs independently. When the interrupt exits, the corresponding context is resumed. Using an independent interrupt stack is relatively easy to implement, and it is easier to understand and grasp the thread stack usage (otherwise it must reserve space for the interrupt stack. If the system supports interrupt nesting, you should also consider how much space should be reserved for nested interrupt?).
RT-Thread adopts interrupt stack that provides independence. When an interrupt occurs, the preprocessor of the interrupt will replace the user's stack pointer into the interrupt stack space reserved by the system in advance, and then restore the user's stack when the interrupt exits. This way, the interrupt does not occupy the stack space of the thread, thereby improving the utilization of the memory space, and as the number of threads increases, the effect of reducing the memory footprint is more obvious.
There are two stack pointers in the Cortex-M processor core. One is the main stack pointer (MSP) which is the stack pointer by default. It is used before the first thread and in the interrupt and exception handlers. The other is the thread stack pointer (PSP), used in threads. When the interrupt and exception service routine exits, modify the value of the second bit of LR register as 1, and the SP of the thread is switched from MSP to PSP.
### Processing of the Bottom Half of the Interruption
RT-Thread does not make any assumptions or restrictions on the processing time required by the interrupt service routine, but like other real-time operating systems or non-real-time operating systems, users need to ensure that all interrupt service routines are completed in the shortest possible time (the interrupt service routine is equivalent to having the highest priority in the system and will preempt all threads to execute first). In the process of interrupt nesting or masking the corresponding interrupt source, the other nested interrupt processing and the next interrupt signal of its own interrupt source will not delayed.
When an interrupt occurs, the interrupt service routine needs to obtain the corresponding hardware state or data. If the interrupt service routine is to perform simple processing on the state or data, such as a CPU clock interrupt, the interrupt service routine only needs to add one to the system clock variable and then terminate the interrupt service routine. Such interrupts often require relatively short running time. However, for other interrupts, the interrupt service routine needs to perform a series of more time-consuming processing after obtaining the hardware state or data. Usually, the interrupt is divided into two parts, the **top half** and the **bottom half**. In the top half, after getting the hardware state and data, open the blocked interrupt, send a notification to the relevant thread (which can be the semaphore, event, mailbox or message queue provided by RT-Thread), and then end the interrupt service program. Then, the relevant thread, after receiving the notification, further processes the state or data, this part of the processing is called bottom half processing.
In order to illustrate the implementation of the bottom half processing in RT-Thread, we take a virtual network device receiving network data packets as an example, as shown in the following code. Assume that after receiving the data message, the system analyzes and processes the message is a relatively time consuming process that is much less important than an external interrupt source signal. It can also be processed without masking the interrupt source signal.
The program in this example creates an nwt thread that will block on the nw_bh_sem signal after it starts to run. Once this semaphore is released, the next nw_packet_parser process will be executed to begin the *Bottom Half* event processing.
```c
/*
* program list: interrupt bottom half processing example
*/
/* semaphore used to wake up threads */
rt_sem_t nw_bh_sem;
/* thread for data reading and analysis */
void demo_nw_thread(void *param)
{
/*First, perform the necessary initialization work on the device. */
device_init_setting();
/*.. other operations..*/
/* create a semaphore to respond to Bottom Half events */
nw_bh_sem = rt_sem_create("bh_sem", 0, RT_IPC_FLAG_FIFO);
while(1)
{
/* Finally, let demo_nw_thread wait on nw_bh_sem. */
rt_sem_take(nw_bh_sem, RT_WAITING_FOREVER);
/* After receiving the semaphore signal, start the real Bottom Half processing. */
nw_packet_parser (packet_buffer);
nw_packet_process(packet_buffer);
}
}
int main(void)
{
rt_thread_t thread;
/* create processing thread */
thread = rt_thread_create("nwt",demo_nw_thread, RT_NULL, 1024, 20, 5);
if (thread != RT_NULL)
rt_thread_startup(thread);
}
```
Let's take a look at how Top Half is handled in demo_nw_isr and how Bottom Half is opened, as in the following example.
```c
void demo_nw_isr(int vector, void *param)
{
/* When the network device receives the data, it is met with an interrupt exception and starts executing this ISR. */
/* Start the processing of the Top Half, such as reading the status of the hardware device to determine what kind of interruption occurred. */
nw_device_status_read();
/*.. Some other data operations, etc. ..*/
/* Release nw_bh_sem, send a signal to demo_nw_thread, ready to start Bottom Half */
rt_sem_release(nw_bh_sem);
/* Then exit the interrupted Top Half section and end the device's ISR */
}
```
As can be seen from the two code snippets of the above example, the interrupt service routine completes the start and end of the interrupt Bottom Half by waiting and releasing a semaphore object. Since the interrupt processing is divided into two parts, Top and Bottom, the interrupt processing becomes an asynchronous process. This part of the system overhead requires the user to seriously consider whether the interrupt service processing time is greater than the time to send notifications to Bottom Half and process when using RT-Thread.
RT-Thread Interrupt Management Interface
---------------------
In order to isolate the operating system from the underlying exceptions and interrupt hardware, RT-Thread encapsulates interrupts and exceptions into a set of abstract interfaces, as shown in the following figure:
![Interrupt Related Interfaces](figures/09interrupt_ops.png)
### Mount Interrupt Service Routine
The system associates the user's interrupt handler with the specified interrupt number. You can call the following interface to mount a new interrupt service routine:
```c
rt_isr_handler_t rt_hw_interrupt_install(int vector,
rt_isr_handler_t handler,
void *param,
char *name);
```
After calling rt_hw_interrupt_install(), when the interrupt source generates an interrupt, the system will automatically call the mounted interrupt service routine. The following table describes the input parameters and return values for this function:
Input parameters and return values of rt_hw_interrupt_install()
|**Parameters **|**Description** |
|----------|--------------------------------------------------|
| vector | vector is the mounted interrupt number |
| handler | newly mounted interrupt service routine |
| param | param is passed as a parameter to the interrupt service routine |
| name | name of the interrupt |
|**Return**| —— |
| return | the handle of the interrupt service routine mounted before the interrupt service routine was mounted |
>This API does not appear in every migration branch. For example, there is usually no such API in the migration branch of Cortex-M0/M3/M4.
The interrupt service routine is a kind of runtime environment that requires special attention. It runs in a non-threaded execution environment (generally a special operating mode of the chip (privileged mode)). In this runtime environment, the current thread cannot be suspended because the current thread does not exist. During the execution of related operations, information similar to print prompt information will appear, "Function [abc_func] shall not used in ISR", meaning a function that should not be called in the interrupt service routine.
### Interrupt Source Management
Usually before the ISR is ready to process an interrupt signal, we need to mask the interrupt source and open the previously blocked interrupt source in time after the ISR finishes processing the status or data.
Masking the interrupt source ensures that the hardware state or data will not be disturbed during the following processing. The following function interface can be called:
```c
void rt_hw_interrupt_mask(int vector);
```
After the rt_hw_interrupt_mask function interface is called, the corresponding interrupt will be masked (usually when this interrupt is triggered, the interrupt status register will change accordingly, but will not be sent to the processor for processing). The following table describes the input parameters for this function:
Input parameters of rt_hw_interrupt_mask()
|**Parameters**|**Description** |
|----------|----------------|
| vector | interrupt number to be masked |
>This API does not appear in every migration branch. For example, there is usually no such API in the migration branch of Cortex-M0/M3/M4.
In order to avoid losing the hardware interrupt signal as much as possible, the following function interface can be called to enable the blocked interrupt source:
```c
void rt_hw_interrupt_umask(int vector);
```
After the rt_hw_interrupt_umask function interface is called, if the interrupt (and corresponding peripheral) is configured correctly, after the interrupt is triggered, it will be sent to the processor for processing. The following table describes the input parameters for this function:
Input parameters of rt_hw_interrupt_umask()
|**Parameters**|**Description** |
|----------|--------------------|
| vector | enable the blocked interrupt number |
>This API does not appear in every migration branch. For example, there is usually no such API in the migration branch of Cortex-M0/M3/M4.
### Global Interrupt Switch
The global interrupt switch, also known as the interrupt lock, is the easiest way to disable multi-threaded access to critical sections by shutting down the interrupts to ensure that the current thread is not interrupted by other events (because the entire system no longer responds to those external events that could trigger a thread rescheduling), that is, the current thread will not be preempted unless the thread voluntarily gives up control of the processor. When you need to shut off the interrupt of the entire system , you can call the following function interface:
```c
rt_base_t rt_hw_interrupt_disable(void);
```
The following table describes the return values for this function:
 Return value of rt_hw_interrupt_disable()
|**Return**|**Description** |
|----------|---------------------------------------------|
| Interrupt Status | interrupt status before the function rt_hw_interrupt_disable runs |
To resume interrupt can also be understood as turn on an interrupt. The rt_hw_interrupt_enable() function is used to "enable" interrupts, which resumes the interrupt state before the rt_hw_interrupt_disable() function is called. If the interrupt state is turned off before the rt_hw_interrupt_disable() function is called, then the interrupt state is still turned off after calling this function. Resuming interrupts are often used in pairs with turning off interrupts. The function interface called is as follows:
```c
void rt_hw_interrupt_enable(rt_base_t level);
```
The following table describes the input parameters for this function:
 Input parameters for rt_hw_interrupt_enable()
|**Parameters**|**Description** |
|----------|---------------------------------------------|
| level | the interrupt status returned by the previous rt_hw_interrupt_disable |
1) The method of using the interrupt lock to operate the critical section can be applied to any occasion, and other types of synchronization methods are implemented relying on the interrupt lock. It can be said that the interrupt lock is the most powerful and efficient synchronization method. The main problem with using interrupt locks is that the system will no longer respond to any interrupts during the interrupt shutdown and will not be able to respond to external events. Therefore, the impact of the interrupt lock on the real-time system is very large. When used improperly, the system will be completely non-real-time (may cause the system to completely deviate from the required time requirement); when used properly, it will become a fast, efficient synchronization.
For example, to ensure that a line of code (such as assignments) is running mutually exclusively , the quickest way is to use interrupt locks instead of semaphores or mutexes:
```c
/* turn off the interrupt */
level = rt_hw_interrupt_disable();
a = a + value;
/* resume interrupt */
rt_hw_interrupt_enable(level);
```
When using an interrupt lock, you need to ensure that the interrupt is turned off for a very short time, such as a = a + value in the above code; you can also switch to another method, such as using semaphores:
```c
/* get semaphore lock */
rt_sem_take(sem_lock, RT_WAITING_FOREVER);
a = a + value;
/* release the semaphore lock */
rt_sem_release(sem_lock);
```
In the implementation of rt_sem_take and rt_sem_release, this code already has the behavior of using interrupt locks to protect semaphore internal variables, so for operations such as a = a + value;, it is more concise and fast to use interrupt locks.
2) The function rt_base_t rt_hw_interrupt_disable(void) and the function void rt_hw_interrupt_enable(rt_base_t level) generally need to be used in pairs to ensure correct interrupt status.
In RT-Thread, the API for switching global interrupts supports multi-level nesting. The code for simple nested interrupts is shown in the following code:
Simple nested interrupt use
```c
#include <rthw.h>
void global_interrupt_demo(void)
{
rt_base_t level0;
rt_base_t level1;
/* The global interrupt is turned off for the first time. The global interrupt status before being turned off may be turned on or off. */
level0 = rt_hw_interrupt_disable();
/* The global interrupt is turned off for the second time. The global interrupt status before being turned off may be turned on or off. */
level1 = rt_hw_interrupt_disable();
do_something();
/* Resume the global interrupt to the state before the second turn-off, so the global interrupt is still turned off after this enable. */
rt_hw_interrupt_enable(level1);
/* Resume the global interrupt to the state before the first turn-off, so the global interrupt status can be on or off. */
rt_hw_interrupt_enable(level0);
}
```
This feature can bring great convenience to the development of the code. For example, if interrupt is turned off in a function, call some sub-functions and then turn on the interrupt. There may also be code for interrupt switch in these subfunctions. Since the API for global interrupts allows the use of nest, users do not need to do special processing for this code.
### Interrupt Notification
When the entire system is interrupted by an interrupt and enters the interrupt handler function, it needs to inform the kernel that it has entered the interrupt state. In this case, the following interfaces can be used:
```c
void rt_interrupt_enter(void);
void rt_interrupt_leave(void);
```
These two interfaces are used respectively in the interrupt preamble and interrupt follow-up procedures, and will both modify the values of rt_interrupt_nest (interrupt nesting depth):
Whenever an interrupt is entered, the rt_interrupt_enter() function can be called to notify the kernel that it has entered the interrupt state and increased the interrupt nesting depth (execute rt_interrupt_nest++);
Whenever an interrupt is exited, the rt_interrupt_leave() function can be called to notify the kernel that it has exited the interrupt state and reduced the interrupt nesting depth (execute rt_interrupt_nest--). Be careful not to call these two interface functions in applications.
The role of using rt_interrupt_enter/leave() is that in the interrupt service routine, if a kernel-related function (such as releasing a semaphore) is called, the kernel can be adjusted in time according to the current interrupt status. For example, if a semaphore is released in the interrupt and a thread is awakened, but the current system is found to be in the interrupt context, then Interrupt during thread switching should be implemented during the thread switching, instead of switching immediately .
However, if the interrupt service routine does not call kernel-related functions (release semaphores, etc.), you may not call the rt_interrupt_enter/leave() function at this time.
In the upper application, the rt_interrupt_get_nest() interface is called when the kernel needs to know that it has entered the interrupt state or the currently nested interrupt depth. It will return rt_interrupt_nest. as follows:
```c
rt_uint8_t rt_interrupt_get_nest(void);
```
The following table describes the return value of rt_interrupt_get_nest()
|**Return**|**Description** |
|----------|--------------------------------|
| 0 | the current system is not in an interrupt context |
| 1 | the current system is in an interrupt context |
| Bigger Than 1 | current interrupt nesting level |
Interrupt and Polling
----------
When the drive peripheral is working, whether the programming mode is triggered by interrupt mode or polling mode is often the first problem to be considered by the driver developer, and there is a difference between the real-time operating system and the time-sharing operating system when it comes to this problem. Because the polling mode itself adopts the sequential execution mode: corresponding processing is done after finding corresponding event. Therefore, the polling mode is relatively simple and clear in terms of implementation. For example, to write data to the serial port, the program code writes the next data only when the serial controller finishes writing a data (otherwise the data is discarded). The corresponding code can look like this:
```c
/* polling mode writes data to the serial port */
while (size)
{
/* Determine if the data in the UART peripheral is sent. */
while (!(uart->uart_device->SR & USART_FLAG_TXE));
/* Send the next data when all data has been sent. */
uart->uart_device->DR = (*ptr & 0x1FF);
++ptr; --size;
}
```
In the real-time system, the polling mode may be very problematic, because in a real-time operating system, when a program is continuously executed (when polling), the thread it is running will always run, and the thread with lower priority will not be running. In a time-sharing system, it will be the opposite. There is almost no difference in priority. You can run this program in one time slice and then run another program on another slice.
So generally, in real-time systems, interrupt mode is mostly used to drive peripherals. When the data has arrived, the relevant processing threads are awoken by the interrupt, and then the subsequent actions are executed . For example, some serial peripherals that carry FIFO (FIFO queue with a certain amount of data) can be written as shown below:
![Interrupt Mode Drive Peripheral](figures/09interrupt_reque.png)
The thread first writes data to the serial port's FIFO. When the FIFO is full, the thread actively suspends. The serial controller continuously fetches data from the FIFO and sends it out at a configured baud rate (for example, 115200 bps). When all data in the FIFO is sent, an interrupt is triggered to the processor; when the interrupt service routine is executed, the thread can be awaken. Here is an example of a FIFO type device. In reality, there are also DMA type devices with similar principles.
For low-speed devices, this mode is very good because the processor can run other threads before the serial peripheral sends the data in the FIFO which improves the overall operating efficiency of the system. (Even for time-sharing systems, such a mode is very necessary.) But for some high-speed devices, such as when the transmission speed reaches 10Mbps, assuming that the amount of data sent at one time is 32 bytes, we can calculate the time required to send such a piece of data: (32 X 8) X 1/10Mbps = 25us. When data needs to be transmitted continuously, the system will trigger an interrupt after 25us to wake up the upper thread to continue the next transmission. Suppose the system's thread switching time is 8us, (usually the real-time operating system's thread context switching only takes a few us) then when the entire system is running, the data bandwidth utilization will be only 25/(25+8) = 75.8%. However, with polling mode, the data bandwidth utilization rate may reach 100%. This is also why people generally think that the data throughput in the real-time system is insufficient. The system overhead is consumed in the thread switching. (some real-time systems may even use the bottom half processing and hierarchical interrupt processing as described earlier in this chapter which means the time overhead of interrupting to the sending thread is lengthened and the efficiency will be further reduced).
Through the above calculation process, we can see some of the key factors: the smaller the amount of transmitted data, the faster the transmission speed, and the greater the impact on data throughput. Ultimately, it depends on how often the system generates interrupts. When a real-time system wants to increase data throughput, there are several ways that can be considered:
1) Increase the length of each data volume for each transmission, and try to let the peripherals send as much data as possible every time;
2) Change the interrupt mode to polling mode if necessary. At the same time, in order to solve the problem that the processor is always preempted with polling mode and other low-priority threads cannot be operated, the priority of the polling thread can be lowered accordingly.
Global Interrupt Switch Usage Example
--------------------
This is an interrupted application routine: when multiple threads access the same variable, use the switch global interrupt to protect the variable, as shown in the following code:
Use switch interrupts to access global variables
```c
#include <rthw.h>
#include <rtthread.h>
#define THREAD_PRIORITY 20
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
/* global variables accessed simultaneously */
static rt_uint32_t cnt;
void thread_entry(void *parameter)
{
rt_uint32_t no;
rt_uint32_t level;
no = (rt_uint32_t) parameter;
while (1)
{
/* turn off glocal interrupt */
level = rt_hw_interrupt_disable();
cnt += no;
/* resume glocal interrupt */
rt_hw_interrupt_enable(level);
rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt);
rt_thread_mdelay(no * 10);
}
}
/* user application entry */
int interrupt_sample(void)
{
rt_thread_t thread;
/* create t1 thread */
thread = rt_thread_create("thread1", thread_entry, (void *)10,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (thread != RT_NULL)
rt_thread_startup(thread);
/* create t2 thread */
thread = rt_thread_create("thread2", thread_entry, (void *)20,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
if (thread != RT_NULL)
rt_thread_startup(thread);
return 0;
}
/* export to the msh command list */
MSH_CMD_EXPORT(interrupt_sample, interrupt sample);
```
The simulation results are as follows:
```
\ | /
- RT - Thread Operating System
/ | \ 3.1.0 build Aug 27 2018
2006 - 2018 Copyright by rt-thread team
msh >interrupt_sample
msh >protect thread[10]'s counter is 10
protect thread[20]'s counter is 30
protect thread[10]'s counter is 40
protect thread[20]'s counter is 60
protect thread[10]'s counter is 70
protect thread[10]'s counter is 80
protect thread[20]'s counter is 100
protect thread[10]'s counter is 110
protect thread[10]'s counter is 120
protect thread[20]'s counter is 140
```
>Since shutting down the global interrupt will cause the entire system to fail to respond to the interrupt, when using the global interrupt as a means of exclusive access to the critical section, it is necessary to ensure that the global interrupt is very short, such as the time to run several machine instructions.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,37 @@
# RT-Thread Introduction
As a beginner of RTOS, you might be new to RT-Thread. However, with a better understanding of it overtime, you will gradually discover the charm of RT-Thread and its advantages over other RTOSs of the same type. RT-Thread is an Embedded Real-time Operating System (RTOS) . After nearly 12 years of experiences accumulated, along with the rise of the Internet of Things, it is evolving into a powerful, component-rich IoT operating system.
## RT-Thread Overview
RT-Thread, short for Real Time-Thread, as its name implies, is an embedded real-time multi-threaded operating system. One of its basic properties is to support multi-tasking. Allowing multiple tasks to run at the same time does not mean that the processor actually performed multiple tasks at the same time. In fact, a processor core can only run one task at a time. Every task is executed quickly, and through the task scheduler (the scheduler determines the sequence according to priority), the tasks are switched rapidly which gives the illusion that multiple tasks are running at the same time. In the RT-Thread system, the task is implemented by threads. The thread scheduler in RT-Thread is the task scheduler mentioned above.
RT-Thread is mainly written in C language, easy to understand and easy to port. It applies object-oriented programming methods to real-time system design, making the code elegant, structured, modular, and very tailorable. For resource-constrained Microcontroller Unit (MCU) systems, NANO version (NANO is a minimum kernel officially released by RT-Thread in July 2017) that requires only 3KB of Flash and 1.2KB of RAM memory resources can be tailored with easy-to-use tools; for resource-rich IoT devices, RT-Thread can use the on-line software package management tool, together with system configuration tools, to achieve intuitive and rapid modular cutting, seamlessly import rich Software f0 eature packs, thus achieving complex functions like Android's graphical interface and touch sliding effects, smart voice interaction effects, and so on.
Compared with the Linux operating system, RT-Thread is small in size, low in cost, low in power consumption and fast in startup. In addition, RT-Thread has high instantaneity and low occupation, which is very suitable for various resource constraints (such as cost, power consumption, etc.). Although the 32-bit MCU is its main operating platform, other CPUs, ones with MMU, ones based on ARM9, ARM11 and even the Cortex-A series CPUs are suitable for RT-Thread in specific applications.
## License Agreement
The RT-Thread system is a completely open source system, the 3.1.0 version and its earlier versions follow the GPL V2 + open source license agreement. Versions from the 3.1.0 version onwards follow the Apache License 2.0 open source license agreement. The RT-Thread system can be used free of charge in commercial products and does not require opening private code to the public.
## RT-Thread Frame
In recent years, the concept of Internet of Things (IoT) has become widely known , and the Internet of Things market has developed rapidly. The networking of embedded devices is the trend of the times. Terminal networking has greatly increased the complexity of software. The traditional RTOS kernel can hardly meet the needs of the market. In this case, the concept of the Internet of Things Operating System (IoT OS) came into being. **IoT operating system refers to the software platform that is based on operating system kernel (like RTOS, Linux, etc.) and includes relatively complete middleware components such as file system, graphics library, etc. It is low in consumption and high in secure, abides by the Communication Protocol and has cloud-connect abilities.** RT-Thread is an IoT OS.
One of the main differences between RT-Thread and many other RTOS such as FreeRTOS and uC/OS is that it is not only a real-time kernel, but also has a rich middle-tier component, as shown in the following figure.
![RT-Thread Software Framework](figures/02Software_framework_diagram.png)
It includes:
- Kernel layer: RT-Thread kernel, the core part of RT-Thread, includes the implementation of objects in the kernel system, such as multi-threading and its scheduling, semaphore, mailbox, message queue, memory management, timer, etc.; libcpu/BSP (Chip Migration Related Files/Board Support Package) is closely related to hardware and consists of peripheral drivers and CPU transport.
- Components and Service Layer: Components are based on upper-level software on top of the RT-Thread kernel, such as virtual file systems, FinSH command-line interfaces, network frameworks, device frameworks, and more. Its modular design allows for high internal cohesion within the assembly and low coupling between components.
- RT-Thread software package: A general-purpose software component running on the RT-Thread IoT operating system platform for different application areas, consisting of description information, source code or library files. RT-Thread provides an open package platform with officially available or developer-supplied packages that provide developers with a choice of reusable packages that are an important part of the RT-Thread ecosystem. The package ecosystem is critical to the choice of an operating system because these packages are highly reusable and modular, making it easy for application developers to build the system they want in the shortest amount of time. RT-Thread supports more than 60 software packages, listed below:
1. Internet of Things related software packages: Paho MQTT, WebClient, mongoose, WebTerminal, etc.
2. Scripting language related software packages: JerryScript and MicroPython are currently supported.
3. Multimedia related software packages: Openmv, mupdf.
4. Tools packages: CmBacktrace, EasyFlash, EasyLogger, SystemView.
5. System related software packages: RTGUI, Persimmon UI, lwext4, partition, SQLite, etc.
6. Peripheral library and driver software packages: RealTek RTL8710BN SDK.
7. Others.

Some files were not shown because too many files have changed in this diff Show More