Lenguaje C integrado (en)

uso volátil

El significado original de volátil es "volátil". La variable declarada con la palabra clave volátil en el entorno incrustado obtendrá el valor de la dirección original cada vez que se haga referencia a su valor. Debido a la naturaleza "variable" de este valor, cualquier asignación u operación de obtención de valor sobre él será ejecutada (no optimizada). Debido a esta característica, esta palabra clave se usa a menudo para eliminar la optimización del compilador en el entorno de compilación integrado. Se puede dividir en los siguientes tres escenarios:

  1. Modificar registros de hardware;
  2. Modificar las variables no automáticas en la función de servicio de interrupción;
  3. Modificar variables que serán modificadas por múltiples aplicaciones en proyectos con sistemas operativos;

Modificación de registros de hardware
Tomando como ejemplo la definición de GPIO en la función de biblioteca HAL de STM32F103, la siguiente es la definición de registro GPIO en la biblioteca HAL:

/**
* @brief General Purpose I/O
*/
typedef struct
{
    
    
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;

La definición de __IO es:

#define __IO volatile /*!< Defines 'read / write' permissions */

Luego defina GPIO como:

#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *)GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *)GPIOG_BASE)

La definición de GPIOx_BASE es así:

#define GPIOA_BASE (APB2PERIPH_BASE + 0x00000800UL)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x00000C00UL)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x00001000UL)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x00001400UL)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x00001800UL)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x00001C00UL)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x00002000UL)

La definición de la dirección base del periférico APB2:

#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)

Finalmente, observe la definición de la dirección base del periférico:

#define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */

En conjunto, expanda las definiciones de macro una por una, solo use GPIOA para ver, las otras se pueden deducir por analogía:

#define GPIOA ((GPIO_TypeDef *)(0x40000000UL + 0x00010000UL + 0x00000800UL))

Después de esta definición, la dirección CRL de GPIOA es:

(volatile uint16_t *)(0x40000000UL + 0x00010000UL + 0x00000800UL)

La dirección de CRH es:

(volatile uint16_t *)(0x40000000UL + 0x00010000UL + 0x00000800UL + 2)

Los siguientes registros se deducen por analogía, por lo que se utilizan en el programa:

GPIOA->CRH |= 0x01;

Entonces, la función realizada es extraer el bit más bajo del registro CRH de GPIOA. Si __IO uint16_t no se usa en la estructura de registro que define GPIO, pero solo se usa uint16_t, entonces use la declaración en el programa:

GPIOA->CRH |= 0x01;

Puede ser optimizado por el compilador y no ejecutar esta declaración, lo que hará que la función de elevar el bit más bajo de CRH sea imposible; pero si se usa volatile en la función de biblioteca para modificar, entonces el compilador no optimizará esta declaración. Cada vez que se ejecuta esta sentencia, el valor será recuperado o almacenado de la dirección de memoria correspondiente al CRH, asegurando que cada ejecución sea válida.

Modificar variables que serán modificadas por múltiples tareas en proyectos con sistemas operativos.
En el desarrollo embebido, no solo hay desarrollo de microcontroladores bare-chip, sino también desarrollo con sistemas operativos. Usualmente los dos usan lenguaje C para desarrollar más. En diseños con sistemas operativos (como RTOS, UCOS-II, Linux, etc.), si múltiples tareas están asignando o recuperando la misma variable, entonces este tipo de variable también debe modificarse con volátiles para asegurar su visibilidad. La llamada visibilidad significa que la tarea actual ha modificado el valor de esta variable y, al mismo tiempo, el valor de esta variable en otras tareas también ha cambiado.

uso de estructura

Uno de los pasos más importantes en el diseño de un programa es elegir una buena forma de representar los datos. En la mayoría de los casos, el uso de variables simples o incluso matrices no es suficiente. C usa variables estructurales para mejorar aún más la capacidad de representar datos. La forma básica de la estructura C es suficiente para representar de manera flexible una variedad de datos y puede crear nuevas formas.

El formato de declaración de la estructura de C es el siguiente:

struct [结构体名] {
    
    
类型标识符 成员名 1;
类型标识符 成员名 2...
类型标识符 成员名 n;
};

Esta declaración describe una estructura compuesta por n miembros de tipo de datos. No crea un objeto de datos real, sino que solo describe de qué está compuesto el objeto. Analizar los detalles de la declaración de estructura. La primera es la palabra clave struct, que indica que se sigue una estructura, seguida de una etiqueta opcional, que se puede usar en programas posteriores para hacer referencia a la estructura, para que podamos seguirla El programa puede ser declarado así:

struct [结构体名] 结构体变量;

Entre llaves en la declaración de estructura está la lista de miembros de la estructura. Cada miembro se describe con su propia declaración. Los miembros pueden ser cualquier tipo de datos C e incluso otras estructuras. El punto y coma después de la llave de cierre es necesario para la declaración, lo que indica el final de la definición del diseño de la estructura, por ejemplo:

struct students
{
    
    
char name[50];
char sex[50];
int age;
float score;
};
int main(void) {
    
    
struct students student;
printf("Name: %s\t",student.name[0]);
printf("Sex: %s\t", student.sex);
printf("Age: %d\t", student.age);
printf("Score: %f\r\n", studen.score);
return 0; }

La declaración de la estructura se puede colocar fuera de todas las funciones o dentro de una función. Si una estructura se declara dentro de una función, entonces su marca se limita al uso interno de la función; si la estructura se declara fuera de todas las funciones, entonces todas las funciones después de la declaración pueden usar su marca.

La estructura tiene dos significados, uno es "diseño estructural", como el structstudent {...} en el ejemplo anterior, le dice al compilador cómo representar los datos, pero no permite que el compilador asigne espacio para los datos; el otro significado es crear una variable de estructura, como la estructura estudiantes estudiante en el ejemplo anterior; el compilador ejecuta esta línea de código para crear una estructura variable estudiante, y el compilador usa la plantilla estudiantes para asignar espacio para la variable: char matriz 1, 50 que contiene 50 elementos Una matriz de tipo char de 2 elementos, una variable int y una variable flotante, estos espacios de almacenamiento se combinan con un estudiante llamado estudiante, como se muestra en la Figura 5.3.3.

Inserte la descripción de la imagen aquí
Los miembros de esta estructura también se almacenan continuamente en la memoria. En la programación normal, struct también se usará con typedef, y los detalles se introducirán en la sección "Uso de Typedef" más adelante.

uso de enumeración

Enum es una palabra clave que se utiliza para modificar variables de tipo de enumeración en el lenguaje C. En lenguaje C, puede usar nombres de símbolo de declaración de tipo de enumeración para representar constantes enteras. Use la palabra clave enum para crear un nuevo "tipo" y especifique el valor que puede tener (de hecho, las constantes enum son de tipo int, siempre y cuando you can Se pueden usar tipos de enumeración donde se usa el tipo int). El propósito del tipo de enumeración es mejorar la legibilidad del programa, y ​​su sintaxis es la misma que la de la estructura, como sigue:

enum [枚举类型名] {
    
    
枚举符 1,
枚举符 2 ...
枚举符 n,
};

P.ej:

enum color
{
    
    
red,
green,
blue,
yellow
};

Constantes de
enumeración En el ejemplo anterior, ¿qué son exactamente rojo, verde, azul y amarillo? Técnicamente hablando, son constantes enteras de tipo int, por ejemplo, se pueden usar así:

printf("red=%d, green=%d", red, green);

Se puede observar que la última información impresa es: rojo = 0, verde = 1. el rojo se convierte en una constante con nombre, que representa el entero 0. De manera similar, otros enumeradores se denominan constantes, que representan 1 ~ 3 respectivamente. Las constantes de enumeración se pueden usar siempre que se puedan usar constantes enteras. Por ejemplo, las constantes de enumeración se pueden usar para indicar el tamaño de la matriz al declarar matrices, y las constantes de enumeración se pueden usar como etiquetas en declaraciones de conmutación.

Valor
predeterminado de enumeración Por defecto, a las constantes en la lista de enumeración se les asigna 0, 1, 2, etc., por lo que en la siguiente declaración, el valor de apple es 2:

enum fruit{
    
    banana, grape, apple};

Asignación de
enumeración En los tipos de enumeración, puede especificar valores enteros para las constantes de enumeración:

enum levels{
    
    low=90, medium=80, high=100};

Si asigna un valor a una sola constante de enumeración y no asigna un valor a la constante de enumeración posterior, a la constante posterior se le asignará el valor posterior, por ejemplo:

enum feline{
    
    cat, lynx=10, puma, tiger};

Entonces cat = 0, los valores de lince, puma, tigre son 10, 11 y 12 respectivamente.

Uso de
typedef La herramienta typedef es una función de datos avanzada. Use typedef para personalizar el nombre de un tipo determinado. Este aspecto es similar a #define, pero hay tres diferencias entre los dos:

  1. A diferencia de #define, los símbolos creados por typedef solo están limitados al tipo y no pueden usarse para valores;
  2. tyedef es interpretado por el compilador, no por el preprocesador;
  3. Dentro de su alcance limitado, typedef es más flexible que #define;

Suponga que desea usar BYTE para representar una matriz de 1 byte, solo necesita definir BYTE como una variable de tipo char y luego agregar la palabra clave typedef antes de la definición:

typedef unsigned char BYTE;

Luego puede usar BYTE para definir variables:

BYTE x, y[10];

El alcance de la definición depende de dónde se defina el typedef. Si está definido en una función, tiene un alcance local y está limitado por la función en la que está definido. Si se define fuera de la función, tiene alcance de archivo.

Crear un nombre para un tipo existente puede parecer redundante, pero a veces puede resultar útil. En el ejemplo anterior, la sustitución de un carácter sin signo por BYTE indica que tiene la intención de utilizar una variable de tipo BYTE para representar números en lugar de caracteres. El uso de typedef también puede mejorar la portabilidad del programa.

Al usar typedef para nombrar un tipo de estructura, puede omitir la etiqueta de estructura (struct):

typedef struct
{
    
    
char name[50];
unsigned int age;
float score;
}student_info;
student_info student={
    
    “Bob”, 15, 90.5};

De esta forma, el nombre del tipo definido por typedef se traducirá a:

struct {
    
    char name[50]; unsigned int age; float score;}
student = {
    
    “Bob”, 15, 90.5};

La segunda razón para usar typedef es: tyedef se usa a menudo para nombrar tipos complejos, como:

typedef void (*pFunction)(void);

Declare pFunction como una función, la función devuelve un puntero, el puntero apunta a un tipo vacío.
Cuando use typdef, recuerde que typedef no crea ningún tipo nuevo, solo agrega una etiqueta conveniente a un tipo existente.

Preprocesadores y directivas de preprocesamiento

Esta sección presentará brevemente el preprocesador de lenguaje C y sus instrucciones de preprocesamiento. La primera son las instrucciones de preprocesamiento, son:

#define、#include、#ifdef、#else、#endif、#ifndef、#if、#elif、#line、#error、#pragma

Entre estas instrucciones, #line, #error, #pragma son relativamente raras en el desarrollo básico, y las otras se encuentran a menudo y se usan con frecuencia en el proceso de programación, por lo que presentaremos principalmente estas instrucciones de uso común en los siguientes capítulos.

El lenguaje C se basa en palabras clave, expresiones y declaraciones apropiadas y las reglas para usarlas. Sin embargo, el estándar C no solo describe el lenguaje C, sino que también describe cómo ejecutar el preprocesador C.

El preprocesador de C examina el programa antes de ejecutarlo, por lo que se denomina preprocesador. De acuerdo con las instrucciones de preprocesamiento en el programa, el preprocesador reemplaza la abreviatura del símbolo con el contenido que representa (#define). El preprocesador puede incluir otros archivos necesarios para el programa (#include), y puede elegir qué código dejar que el compilador vea (compilación condicional). El preprocesador no conoce C, y básicamente su trabajo es convertir un texto en otro texto.

Dado que la longitud de la expresión de preprocesamiento debe ser una línea lógica (puede utilizar el carácter de nueva línea '\' para convertir la línea lógica en varias líneas físicas), para que el preprocesador obtenga la línea lógica correcta, habrá Durante una proceso de compilación, el compilador ubica cada barra invertida seguida de un ejemplo de nueva línea y las elimina, como por ejemplo:

printf(“Hello, Chi\
na”);

Convertido en una línea lógica:

printf(“Hello, China”);

Además, el compilador divide el texto en secuencia de token de preprocesamiento, secuencia en blanco y secuencia de comentarios (un token es un elemento separado por espacios, tabulaciones o nuevas líneas). Debe tenerse en cuenta que el compilador reemplazará cada entrada con un carácter de espacio Notas , por ejemplo:

char/*这是一条注释*/str;

Se convertirá:

char str;

Después de compilar y procesar de esta manera, el programa está listo para entrar en la etapa de preprocesamiento y el preprocesador busca instrucciones de preprocesamiento que comiencen con # en una línea. Luego comenzamos a explicar estas directivas de preprocesamiento de la directiva #define.

El archivo incluye #include

Cuando el preprocesador encuentra la directiva de preprocesamiento #include, buscará el siguiente nombre de archivo e incluirá el contenido del archivo en el archivo actual, es decir, reemplazará la directiva #include en el archivo. Esto equivale a ingresar todo el contenido del archivo incluido en la ubicación donde se encuentra la directiva #include del archivo fuente.

La directiva #include tiene dos formas:

#include <stdio.h> // 文件名在尖括号内
#include “myfile.h” // 文件名在双引号内

En UNIX, los corchetes angulares <> le dicen al preprocesador que busque el archivo en el directorio del sistema estándar, y las comillas dobles "" le dicen al preprocesador que busque el archivo en el directorio actual (o el directorio con la ruta especificada) primero, y luego búsquelo si no lo encuentra. Catálogo del sistema estándar:

#include <stdio.h> // 在标准系统目录中查找 stdio.h 文件
#include “myfile.h” // 在当前目录中查找 myfile.h 文件
#include “/project/header.h” // 在 project 目录查找
#include “../myheader.h” // 在当前文件的上一级目录查找

El entorno de desarrollo integrado (IDE, como el entorno de desarrollo keil de la placa de desarrollo) también tiene una ruta estándar o una ruta de archivo de encabezado del sistema. Muchos entornos integrados ofrecen opciones de menú para especificar la ruta de búsqueda cuando se utilizan corchetes angulares.

¿Por qué necesitamos incluir archivos? Debido a que el compilador necesita la información en estos archivos, por ejemplo, stdio.h generalmente contiene las definiciones de EOF, NULL, getchar () y putchar (). Además, el archivo también contiene otras funciones de C I / O. Para nuestros archivos personalizados, para el desarrollo incrustado, estos archivos pueden tener algunas definiciones de macros de pines, definiciones de macros de funciones simples, etc. que deben usarse, así como las variables y funciones globales de un determinado archivo fuente. Declaración, etc.

El lenguaje C se usa para usar el sufijo .h para indicar archivos de encabezado, que contienen información que debe colocarse en la parte superior del programa. Los archivos de encabezado a menudo contienen algunas instrucciones de preprocesamiento. Algunos archivos de encabezado los proporciona el sistema o se pueden personalizar.

El siguiente es un ejemplo de cómo personalizar un archivo de encabezado: gpio.h, main.c

/*gpio.h*/
#ifndef __GPIO_H
#define __GPIO_H
#include <stdio.h>
typedef struct
{
    
    
uint8_t cnt;
uint16_t sum;
float result;
}MyStruct;
typedef enum
{
    
    
GPIO_RESET = 0,
GPIO_SET = 1,
}GPIO_STATE;
#define ABS(x) ((x>0) ? (x) : (-x))
#endif
/* main.c */
#include “gpio.h”
int main(void) {
    
    
MyStruct my_struct = {
    
    0, 25, 3.14};
GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_SET);
printf(“cnt=%d, sum=%d, result=%f\n\r”, my_struct.cnt, my_struct.sum, my_struct.result); }

La directiva #include no solo incluye archivos .h, también puede incluir archivos .c.


Foro de tecnología de Baiwen:
http://bbs.100ask.net/

Sitio web oficial de video incrustado de Baiwen.com:
https://www.100ask.net/index

Placa de desarrollo de Baiwen.com:
Taobao: https://100ask.taobao.com/Tmall
: https://weidongshan.tmall.com/

Grupo de intercambio de tecnología (Desarrollo de
Hongmeng / Linux / Embebido / Controlador / Descarga de datos) Grupo QQ: 869222007

Grupo de intercambio de Linux integrado en MCU:
Grupo QQ: 536785813

Supongo que te gusta

Origin blog.csdn.net/thisway_diy/article/details/115295522
Recomendado
Clasificación