c - poner - tags en wordpress
Buenas manipulaciones de buenas prácticas. (7)
Como programador principiante de C, me pregunto cuál sería la mejor solución fácil de leer y entender para configurar bits de control en un dispositivo. ¿Hay algún estándar ? ¿Algún código de ejemplo para imitar? Google no dio ninguna respuesta confiable.
Por ejemplo, tengo un mapa de bloque de control:
La primera manera que veo sería simplemente establecer los bits necesarios. Requiere un montón de explicaciones en los comentarios y no parece ser tan profesional.
DMA_base_ptr[DMA_CONTROL_OFFS] = 0b10001100;
La segunda forma que veo es crear un campo de bits. No estoy seguro de si este es el método al que debo atenerme, ya que nunca encontré que se usara de esa manera (a diferencia de la primera opción que mencioné).
struct DMA_control_block_struct
{
unsigned int BYTE:1;
unsigned int HW:1;
// etc
} DMA_control_block_struct;
¿Es una de las opciones mejor que la otra? ¿Hay alguna opción que simplemente no veo?
Cualquier consejo sería altamente apreciado
Debe asegurarse de inicializar los bits a un valor predeterminado conocido cuando declare la variable para almacenar sus valores. En C
, cuando declara una variable, simplemente está reservando un bloque de memoria en una dirección y el tamaño del bloque se basa en su tipo. Si no inicializa la variable, puede encontrar un comportamiento indefinido / inesperado, ya que el valor de la variable se verá afectado por el valor / estado de la memoria en ese bloque antes de que lo declarara. Al inicializar la variable a un valor predeterminado, está limpiando este bloque de memoria de su estado existente y lo está poniendo en un estado conocido.
En cuanto a la legibilidad, debe utilizar un campo de bit para almacenar los valores del bit. Un campo de bits le permite almacenar los valores de los bits en una estructura. Esto facilita la organización, ya que puede usar la notación de puntos. Además, debe asegurarse de comentar la declaración del campo de bits para explicar para qué se utilizan los diferentes campos como una mejor práctica. Espero que esto responda tu pregunta. Buena suerte con tu programación en C
!
El problema con los campos de bits es que el estándar C no dicta que el orden en que se definen sea el mismo que el orden en que se implementan. Así que puede que no estés configurando los bits que crees que eres.
La sección 6.7.2.1p11 de la norma C establece:
Una implementación puede asignar cualquier unidad de almacenamiento direccionable lo suficientemente grande como para contener un campo de bits. Si queda suficiente espacio, un campo de bits que sigue inmediatamente a otro campo de bits en una estructura se empaquetará en bits adyacentes de la misma unidad. Si no queda espacio suficiente, si un campo de bits que no se ajusta se coloca en la siguiente unidad o se superpone a las unidades adyacentes se define por la implementación. El orden de asignación de los campos de bits dentro de una unidad (orden alto a orden bajo o orden bajo a orden alto) está definido por la implementación. La alineación de la unidad de almacenamiento direccionable no está especificada.
Como ejemplo, vea la definición de struct iphdr
, que representa un encabezado IP, del archivo de archivo /usr/include/netinet/ip.h en Linux:
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
u_int8_t tos;
...
Aquí puede ver que los campos de bits se colocan en un orden diferente según la implementación. Tampoco debe utilizar esta comprobación específica porque este comportamiento depende del sistema. Es aceptable para este archivo porque es parte del sistema. Otros sistemas pueden implementar esto de diferentes maneras.
Así que no uses un campo de bits.
La mejor manera de hacer esto es establecer los bits requeridos. Sin embargo, tendría sentido definir constantes con nombre para cada bit y realizar un OR a nivel de bits de las constantes que desea establecer. Por ejemplo:
const uint8_t BIT_BYTE = 0x1;
const uint8_t BIT_HW = 0x2;
const uint8_t BIT_WORD = 0x4;
const uint8_t BIT_GO = 0x8;
const uint8_t BIT_I_EN = 0x10;
const uint8_t BIT_REEN = 0x20;
const uint8_t BIT_WEEN = 0x40;
const uint8_t BIT_LEEN = 0x80;
DMA_base_ptr[DMA_CONTROL_OFFS] = BIT_LEEN | BIT_GO | BIT_WORD;
La forma en C de la vieja escuela es definir un montón de bits:
#define WORD 0x04
#define GO 0x08
#define I_EN 0x10
#define LEEN 0x80
Entonces tu inicialización se convierte en
DMA_base_ptr[DMA_CONTROL_OFFS] = WORD | GO | LEEN;
Puede configurar bits individuales utilizando |
:
DMA_base_ptr[DMA_CONTROL_OFFS] |= I_EN;
Puede borrar bits individuales usando &
y ~
:
DMA_base_ptr[DMA_CONTROL_OFFS] &= ~GO;
Puedes probar bits individuales usando &
:
if(DMA_base_ptr[DMA_CONTROL_OFFS] & WORD) ...
Definitivamente no use campos de bits, sin embargo. Tienen sus usos, pero no cuando una especificación externa define que los bits están en ciertos lugares, como supongo que es el caso aquí.
Vea también las preguntas 20.7 y 2.26 en la lista de C Preguntas frecuentes .
Los compiladores de C modernos manejan funciones triviales en línea muy bien, sin gastos generales. Haría todas las funciones de abstracción, de modo que el usuario no necesite manipular ningún bit o número entero, y es poco probable que abuse de los detalles de la implementación.
Por supuesto, puede usar constantes y no funciones para los detalles de implementación, pero la API debe ser funciones. Esto también permite usar macros en lugar de funciones si está usando un compilador antiguo.
Por ejemplo:
#include <stdbool.h>
#include <stdint.h>
typedef union DmaBase {
volatile uint8_t u8[32];
} DmaBase;
static inline DmaBase *const dma1__base(void) { return (void*)0x12340000; }
// instead of DMA_CONTROL_OFFS
static inline volatile uint8_t *dma_CONTROL(DmaBase *base) { return &(base->u8[12]); }
// instead of constants etc
static inline uint8_t dma__BYTE(void) { return 0x01; }
inline bool dma_BYTE(DmaBase *base) { return *dma_CONTROL(base) & dma__BYTE(); }
inline void dma_set_BYTE(DmaBase *base, bool val) {
if (val) *dma_CONTROL(base) |= dma__BYTE();
else *dma_CONTROL(base) &= ~dma__BYTE();
}
inline bool dma1_BYTE(void) { return dma_BYTE(dma1__base()); }
inline void dma1_set_BYTE(bool val) { dma_set_BYTE(dma1__base(), val); }
Dicho código debe ser generado por una máquina: yo uso gsl
(de 0mq fame) para generar aquellos basados en una plantilla y alguna entrada XML que enumera los detalles de los registros.
No hay estándar para campos de bits. El mapeo y la operación de bits dependen del compilador en este caso. Los valores binarios como 0b0000
no están estandarizados también. La forma habitual de hacerlo es definir valores hexadecimales para cada bit. Por ejemplo:
#define BYTE (0x01)
#define HW (0x02)
/*etc*/
Cuando desee establecer bits, puede utilizar:
DMA_base_ptr[DMA_CONTROL_OFFS] |= HW;
O puedes borrar bits con:
DMA_base_ptr[DMA_CONTROL_OFFS] &= ~HW;
Otras respuestas ya cubrieron la mayoría de las cosas, pero vale la pena mencionar que incluso si no puede usar la sintaxis 0b
no estándar, puede usar turnos para mover el bit 1
a la posición por número de bit, es decir:
#define DMA_BYTE (1U << 0)
#define DMA_HW (1U << 1)
#define DMA_WORD (1U << 2)
#define DMA_GO (1U << 3)
// …
Observe cómo el último número coincide con la columna "número de bit" en la documentación.
El uso para configurar y borrar bits no cambia:
#define DMA_CONTROL_REG DMA_base_ptr[DMA_CONTROL_OFFS]
DMA_CONTROL_REG |= DMA_HW | DMA_WORD; // set HW and WORD
DMA_CONTROL_REG &= ~(DMA_BYTE | DMA_GO); // clear BYTE and GO
Podrías usar campos de bits, a pesar de lo que todos los intrigantes han estado diciendo aquí. Solo necesita saber cómo los compiladores y las ABI del sistema con los que desea que su código funcione, definen los aspectos "definidos por la implementación" de los campos de bits. No se asuste con los pedantes que ponen en negrita las palabras como "implementación definida".
Sin embargo, lo que otros hasta ahora parecen haberse perdido son los diversos aspectos de cómo se comportan los dispositivos de hardware mapeados en memoria que pueden ser contraintuitivos cuando se trata de un lenguaje de nivel superior como C y las funciones de optimización que ofrecen dichos lenguajes. Por ejemplo, cada lectura o escritura de un registro de hardware puede tener efectos secundarios a veces incluso si los bits no se cambian en la escritura. Mientras tanto, el optimizador puede hacer que sea difícil saber cuándo el código generado está realmente leyendo o escribiendo en la dirección del registro, e incluso cuando el objeto C que describe el registro está calificado como volatile
, se requiere mucho cuidado para controlar cuándo E / S ocurre.
Quizás necesite usar alguna técnica específica definida por su compilador y su sistema para manipular adecuadamente los dispositivos de hardware asignados en memoria. Este es el caso de muchos sistemas embebidos. En algunos casos, los proveedores de compiladores y sistemas utilizarán los campos de bits, al igual que Linux en algunos casos. Sugeriría leer el manual de su compilador primero.
La tabla de descripción de bits que cita parece ser para el registro de control del núcleo del controlador Intel Avalon DMA. La columna "leer / escribir / borrar" da una pista sobre cómo se comporta un bit en particular cuando se lee o escribe. El registro de estado para ese dispositivo tiene un ejemplo de un bit en el que escribir un cero borrará un valor de bit, pero es posible que no vuelva a leer el mismo valor que se escribió, es decir, escribir el registro puede tener un efecto secundario en el dispositivo. Dependiendo del valor del bit DONE. Es interesante que documentan el bit SOFTWARERESET como "RW", pero luego describen el procedimiento escribiendo un 1 en él dos veces para activar el reinicio, y luego advierten que al ejecutar un reinicio del software DMA cuando la transferencia DMA está activa, puede producirse un bloqueo permanente del bus. (hasta el siguiente reinicio del sistema). Por lo tanto, el bit SOFTWARERESET no debe escribirse excepto como último recurso. Administrar un restablecimiento en C requeriría una codificación cuidadosa, sin importar cómo describa el registro.
En cuanto a las normas, el pozo ISO / IEC ha producido un "informe técnico" conocido como "ISO / IEC TR 18037" , con el subtítulo "Extensiones para admitir procesadores integrados" . Discute una serie de problemas relacionados con el uso de C para administrar el direccionamiento de hardware y la E / S del dispositivo, y específicamente para los tipos de registros de mapa de bits que menciona en su pregunta que documenta una serie de macros y técnicas disponibles a través de un archivo de inclusión. llamar <iohw.h>
. Si su compilador proporciona un archivo de encabezado de este tipo, es posible que pueda usar estas macros.
Hay versiones preliminares de TR 18037 disponibles, la última es TR 18037 (2007) , aunque ofrece una lectura bastante seca. Sin embargo, contiene una implementación de ejemplo de <iohw.h>
.
Quizás un buen ejemplo de una implementación <iohw.h>
mundo real está en QNX. La documentación de QNX ofrece una visión general decente (y un ejemplo, aunque sugeriría encarecidamente usar enum
s para valores enteros, nunca macros): QNX <iohw.h>