import RT-Thread@9217865c without bsp, libcpu and components/net
263
documentation/device/adc/adc.md
Normal file
|
@ -0,0 +1,263 @@
|
|||
# ADC Device
|
||||
|
||||
## An Introduction to ADC
|
||||
|
||||
An ADC (analog-to-digital converter) is a hardware device that converts continuously changing analog signals to discrete digital signals. Usually, these analog signals include temperature, pressure, sound, video and many other types of signals. Converting them is important, as digital signals are easier to store, process, and transmit. This conversion can be achieved by using an ADC device which is commonly integrated in various platforms. Historically, ADCs were first used to convert received wireless signals to digital signals, for example, 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, while quantization and encoding are implemented simultaneously in the conversion process.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
Resolution is represented as binary (or decimal) numbers. Generally, it comes in 8 bits, 10 bits, 12 bits, 16 bits, etc. A larger resolution, in bits, means more accuracy in the conversion of analog to digital signals.
|
||||
|
||||
### Precision
|
||||
|
||||
Precision is the maximum error value between analog signals and real ADC device numerical points’ values.An ADC with a high resolution might have a low precision, meaning that factors like noise can affect the numerical ADC reading more than small changes in the input signal.
|
||||
|
||||
### Conversion Rate
|
||||
|
||||
The conversion rate is the reciprocal of time taken for an ADC device to complete conversion from an analog to a digital signal. For example, an ADC device with a conversion rate of 1MHz means that the ADC 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 handler based on the ADC device name to operate the ADC device. Following is the interface function to find the devices:
|
||||
|
||||
```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 a nutshell, the names of the ADC devices are registered as adc0, adc1, and so on. What follows is an example on how to use it
|
||||
|
||||
```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
|
||||
|
||||
It is required to enable the ADC device with the following interface function before reading and operating the ADC 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*.
|
||||
|
||||
### Disabling the ADC Channel
|
||||
|
||||
An ADC channel can be disabled through the following function:
|
||||
|
||||
```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
|
||||
|
||||
To find out the registered device, you can use the command adc probe followed by the registered ADC device name as following,
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
|
||||
|
BIN
documentation/device/adc/figures/adc-p.png
Normal file
After Width: | Height: | Size: 31 KiB |
486
documentation/device/device.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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_ssize_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
|
||||
rt_ssize_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:
|
||||
|
||||

|
||||
|
||||
### 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;
|
||||
}
|
||||
```
|
||||
|
BIN
documentation/device/figures/block-dev.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
documentation/device/figures/io-call.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
documentation/device/figures/io-dev.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
documentation/device/figures/io-fun-call.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
documentation/device/figures/io-parent.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
documentation/device/figures/wtd-uml.png
Normal file
After Width: | Height: | Size: 46 KiB |
401
documentation/device/hwtimer/hwtimer.md
Normal file
|
@ -0,0 +1,401 @@
|
|||
# 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.
|
||||
|
||||
**Timer mode**: Counts the internal pulse. Timers are often used as timing clocks for timing detection, timing response, and timing control.
|
||||
|
||||
**Counter mode**: The counter can count up or down. The maximum count value of a 16-bit counter is 65535, and the maximum value of a 32-bit counter is 4 294 967 295.
|
||||
|
||||
**Counting frequency**:Since the input frequency is usually fixed, the time it takes for the counter to reach its desired count number can be calculated from just the given frequency - `time = count value / count frequency`. For example, if the counting frequency is 1 MHz, the counter counts once every 1 / 1000000 seconds. That is, every 1 microsecond, the counter is incremented by one (or subtracted by one), at this time, the maximum timing capability of the 16-bit counter is 65535 microseconds, or 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 value:RT_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 with the following function - this is the function that will be called when the timer reaches its set count value:
|
||||
|
||||
```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 sending control words, the application can configure the hardware timer device with 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 failed,dev 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 argument, 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 included driver set the counting frequency. Generally, the default frequency of the driving setting can be used.
|
||||
|
||||
When setting the timer mode, the parameter argument 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 with 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 |
|
||||
|
||||
When a timer device has been used and is not necessary anymore, it should be closed, otherwise the device will remain in an open 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);
|
||||
```
|
BIN
documentation/device/i2c/figures/i2c1.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
documentation/device/i2c/figures/i2c2.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
documentation/device/i2c/figures/i2c3.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
298
documentation/device/i2c/i2c.md
Normal 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). Compared to the SPI bus, which has two lines for receiving data and transmitting data between the master and slave devices, 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 to communicate at a time. As shown below:
|
||||
|
||||

|
||||
|
||||
The main data transmission format of the I2C bus is shown in the following figure:
|
||||
|
||||

|
||||
|
||||
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 (the host can 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:
|
||||
|
||||

|
||||
|
||||
* **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);
|
||||
```
|
||||
|
BIN
documentation/device/pin/figures/pin2.png
Normal file
After Width: | Height: | Size: 16 KiB |
353
documentation/device/pin/pin.md
Normal 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 pins are further divided into General Purpose Input Output (GPIO) and function-multiplexed I/O (such as SPI/I2C/UART, etc.) pins, referring to their usage mode.
|
||||
|
||||
Most MCU pins have more than one function. Their internal structure is different and their supported functionality 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:
|
||||
|
||||

|
||||
|
||||
* 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, which not the same. The pin numbers are defined by the PIN device driver and are related to the specific chip used. 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 functionality of a pin, you can use the following function to configure the 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 format:pin_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);
|
||||
```
|
BIN
documentation/device/pwm/figures/pwm-f.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
documentation/device/pwm/figures/pwm-l.png
Normal file
After Width: | Height: | Size: 10 KiB |
260
documentation/device/pwm/pwm.md
Normal file
|
@ -0,0 +1,260 @@
|
|||
# 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.
|
||||
|
||||

|
||||
|
||||
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 a light or screen. The brightness can be adjusted through changing the duty cycle. The PWM does not adjust the light continuously, rather it constantly turns the screen on and off. 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 switching it on and off, the longer the light is off, the lower the brightness of the screen to the naked eye. Conversely, the longer the light is on, the brighter the screen will appear.
|
||||
|
||||

|
||||
|
||||
## 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*/
|
||||
|
||||
/* 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);
|
||||
```
|
198
documentation/device/rtc/rtc.md
Normal 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. RTCs find many uses in IoT scenarios, and even in secure transmission processes 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 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 format:rtc_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);
|
||||
```
|
465
documentation/device/sensor/sensor.md
Normal file
|
@ -0,0 +1,465 @@
|
|||
# Sensor Device
|
||||
|
||||
## Introduction
|
||||
|
||||
Sensors are an important part of the Internet of Things, and sensors in an IoT system are equivalent to the eyes of humans. Without eyes, human beings can not see and interpret the world around them. The same is true for the Internet of Things.
|
||||
|
||||
Nowadays, with the development of IoT, a large number of sensors are available for developers to choose, such as Accelerometers, Magnetometers, Gyroscopes, Barometers/pressure senosrs, Hygrometers/humidity meters and so on. However, 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, developers need to write a device driver for each sensor, which can be difficult and time-consuming. In order to reduce the difficulty of application development and increase the reusability of sensor driver, we designed a Sensor device.
|
||||
|
||||
The function of the Sensor device is to provide a unified operation interface for the upper layer and improve the reusability of the upper code.
|
||||
|
||||
### Characteristics of the Sensor Device
|
||||
|
||||
- **Interface:** Standard device interface (open/close/read/control)
|
||||
- **Work mode:** Supports polling, interrupts, three FIFO (First In, First Out) 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 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 transmission mode needs sensor hardware support, data is stored in hardware FIFO, which reads and stores multiple data simultaneously, which allows the CPU to do other operations while gathering data. This feature is 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 words, 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 | failed,device 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 Data,unit 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, ®);
|
||||
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, as the sensor receives data the callback function will be called. 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 Format:sensor_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);
|
||||
```
|
||||
|
BIN
documentation/device/spi/figures/spi1.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
documentation/device/spi/figures/spi2.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
documentation/device/spi/figures/spi5.png
Normal file
After Width: | Height: | Size: 46 KiB |
741
documentation/device/spi/spi.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
* 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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
**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);
|
||||
```
|
||||
|
BIN
documentation/device/uart/figures/uart-dma.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
documentation/device/uart/figures/uart-int.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
documentation/device/uart/figures/uart1.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
646
documentation/device/uart/uart.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
* 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 as:RT_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:
|
||||
|
||||

|
||||
|
||||
|
||||
```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:
|
||||
|
||||

|
||||
|
||||
```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);
|
||||
```
|
||||
|
228
documentation/device/watchdog/watchdog.md
Normal 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 time,the system does not go wrong,otherwise 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);
|
||||
```
|
||||
|
BIN
documentation/device/wlan/figures/an0026_1.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
documentation/device/wlan/figures/an0026_3.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
documentation/device/wlan/figures/an0026_4.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
documentation/device/wlan/figures/an0026_5.png
Normal file
After Width: | Height: | Size: 53 KiB |
443
documentation/device/wlan/wlan.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|