Using Task State Segment to handle ring 0 int - c

I've been working from some time on easy os kernel. So far I've properly setup IDT with one INT (software).
Next step was to try to get to ring 3 of kernel. This was dane by me using 'trick' with iret ring switch was successful, as
state of segment register indicate. Problems occurred when I tried to use int $20 to switch to ring 0 to handle this interrupt.
Of course I have setup TSS to be able to do such action. Here is some code:
This function encodes descriptor (I've enclosed this because I think this can be problem with encoding)
void register_descriptor(unsigned entryIdx, uint32_t base, uint32_t limit, uint16_t flag)
{
uint64_t descriptor;
descriptor = limit & 0x000F0000;
descriptor |= (flag << 8) & 0x00F0FF00;
descriptor |= (base >> 16) & 0x000000FF;
descriptor |= base & 0xFF000000;
descriptor <<= 32;
descriptor |= base << 16;
descriptor |= limit & 0x0000FFFF;
GDT[entryIdx] = descriptor;
}
TSS loading to GDT (zeroTSS() just set all members of TSS struct to 0):
static void write_tss(unsigned num, uint16_t ss0, uint32_t esp0)
{
register_descriptor(num, (uint32_t) &tss_entry, sizeof(tss_entry), (TSS));
zeroTSS();
tss_entry.ss0 = ss0;
tss_entry.esp0 = esp0;
tss_entry.cs = 0x18;
tss_entry.ss = tss_entry.ds = tss_entry.es = tss_entry.fs = tss_entry.gs = 0x20;
}
TSS Flush(My GDT have 6 entries (NULL+TSS+4 Segment)):
.global flush_tss
flush_tss:
mov $0x28, %ax
ltr %ax
ret
And interupt handler register:
IDTEntry fillIDTEntry(uint32_t intHandler,
uint16_t selector,
uint8_t type_attr)
{ IDTEntry newEntry;
newEntry.offset_low = LOW_FUN_ADDR(intHandler);
newEntry.selector = selector;
newEntry.zero = 0;
newEntry.type_attr = type_attr;
newEntry.offset_up = UP_FUN_ADDR(intHandler);
return newEntry;
}
extern void _lidt(_IDT_PTR* idtPtr);
void loadIDT()
{
zeroIDT();
_IDT_PTR idtPtr;
idtPtr.idtSize = sizeof(struct __InteruptDescriptorTableEntry)*256 - 1;
idtPtr.idtBaseAddr = (uint32_t) &InteruptDescriptorTable;
IDTEntry printOnScreenInt = fillIDTEntry((uint32_t)interupt_pritnOnScreen, 0x18, 0x8e);
registerInterupt(printOnScreenInt, 32);
_lidt(&idtPtr);
}
.global _lidt
_lidt:
push %ebp
mov %esp,%ebp
mov 8(%esp), %eax
lidt (%eax)
leave
ret
Finally print form Bochs:
[CPU0 ] interrupt(): soft_int && (gate.dpl < CPL)
[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
[CPU0 ] CPU is in protected mode (active)
[CPU0 ] CS.mode = 32 bit
[CPU0 ] SS.mode = 32 bit
[CPU0 ] EFER = 0x00000000
[CPU0 ] | EAX=00100d48 EBX=001058b4 ECX=00000720 EDX=001058c2
[CPU0 ] | ESP=0010589c EBP=0010589c ESI=00101000 EDI=00000000
[CPU0 ] | IOPL=0 ID vip vif ac vm RF nt of df if tf sf zf af PF cf
[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
[CPU0 ] | CS:000b( 0001| 0| 3) 00000000 ffffffff 1 1
[CPU0 ] | DS:0013( 0002| 0| 3) 00000000 ffffffff 1 1
[CPU0 ] | SS:0013( 0002| 0| 3) 00000000 ffffffff 1 1
[CPU0 ] | ES:0013( 0002| 0| 3) 00000000 ffffffff 1 1
[CPU0 ] | FS:0013( 0002| 0| 3) 00000000 ffffffff 1 1
[CPU0 ] | GS:0013( 0002| 0| 3) 00000000 ffffffff 1 1
[CPU0 ] | EIP=001008ed (001008ed)
[CPU0 ] | CR0=0x60000011 CR2=0x00000000
[CPU0 ] | CR3=0x00000000 CR4=0x00000000
[CPU0 ] 0x001008ed>> int 0x20 : CD20
I will only mention that before introducing two privilege levels this interrupt handler was working.

Related

GDT segment reload failed

I'm writing a little kernel in c for x86 platform, but I'm having trouble to load the gdt and reload the segment selectors.
I am using bochs to test my kernel.
The issue is, when I load the GDT but don't reload the segment selectors, I can stop my program, type info gdt and get a nice result:
When I dont load my GDT:
<bochs:2> info gdt
Global Descriptor Table (base=0x00000000000010b0, limit=32):
GDT[0x0000]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x0008]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x0010]=Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, Non-Conforming, Accessed, 32-bit
GDT[0x0018]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
<bochs:3>
When I load my GDT:
<bochs:2> info gdt
Global Descriptor Table (base=0x00000000001022a0, limit=48):
GDT[0x0000]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x0008]=Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, Non-Conforming, 32-bit
GDT[0x0010]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write
GDT[0x0018]=Code segment, base=0x00000000, limit=0x00000fff, Execute-Only, Non-Conforming, 32-bit
GDT[0x0020]=Data segment, base=0x00000000, limit=0x00000fff, Read-Only
GDT[0x0028]=??? descriptor hi=0x00000000, lo=0x00000000
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
<bochs:3>
So it seems that my GDT is loaded properly.
Now comes the tricky part.
When I want to reload the segment selectors, I'm having this error:
04641352650e[CPU0 ] fetch_raw_descriptor: GDT: index (ff57) 1fea > limit (30)
04641352650e[CPU0 ] interrupt(): vector must be within IDT table limits, IDT.limit = 0x0
04641352650e[CPU0 ] interrupt(): vector must be within IDT table limits, IDT.limit = 0x0
04641352650i[CPU0 ] CPU is in protected mode (active)
04641352650i[CPU0 ] CS.mode = 32 bit
04641352650i[CPU0 ] SS.mode = 32 bit
04641352650i[CPU0 ] EFER = 0x00000000
04641352650i[CPU0 ] | EAX=0000ff53 EBX=00010000 ECX=001022e0 EDX=00000000
04641352650i[CPU0 ] | ESP=00102294 EBP=001022b0 ESI=00000000 EDI=00000000
04641352650i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af PF cf
04641352650i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
04641352650i[CPU0 ] | CS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | DS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | SS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | ES:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | FS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | GS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | EIP=001001d8 (001001d8)
04641352650i[CPU0 ] | CR0=0x60000011 CR2=0x00000000
04641352650i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
(0).[4641352650] [0x0000001001d8] 0010:00000000001001d8 (unk. ctxt): mov ds, ax ; 8ed8
04641352650e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
And with that, when I type info gdt again, it gives me a very huge array,
which doesn't even fit in my terminal scrollback capacity.
Here are the last lines:
GDT[0xffd8]=??? descriptor hi=0x72670074, lo=0x64696c61
GDT[0xffe0]=16-Bit TSS (available) at 0x6c65725f, length 0xc6275
GDT[0xffe8]=Data segment, base=0x5f726700, limit=0x0002636f, Read-Only, Expand-down, Accessed
GDT[0xfff0]=Data segment, base=0x00657266, limit=0x00086572, Read/Write, Accessed
GDT[0xfff8]=Data segment, base=0x675f6275, limit=0x00057267, Read/Write
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
It says me that I want to access data outside of my GDT.
Here is the code I have written so far:
enum SEG_TYPE {
// Data
SEG_TYPE_DRO = 0b0000,
SEG_TYPE_DRW = 0b0010,
SEG_TYPE_DROE = 0b0100,
SEG_TYPE_DRWE = 0b0110,
// Code
SEG_TYPE_CEO = 0b1000,
SEG_TYPE_CER = 0b1010,
SEG_TYPE_CEOC = 0b1100,
SEG_TYPE_CERC = 0b1110,
};
enum SEG_AC {
SEG_AC_KERNEL = 0b11,
SEG_AC_USER = 0b00,
};
void gdt_entry_init(struct gdt_entry* entry, u32 base, u32 limit, enum SEG_TYPE type, enum SEG_AC access_rights) {
// Base address
entry->base_0_15 = base;
entry->base_16_23 = base >> 16;
entry->base_24_31 = base >> 24;
// Limit
entry->limit_0_15 = limit;
entry->limit_16_19 = limit >> 16;
// Segment type
entry->type = type;
// Access rights
entry->dpl = access_rights;
// AVL
entry->avl = 0;
// Default operation set to 32 bits
entry->db = 1;
// Code segment
entry->l = 0;
// Present (always present)
entry->p = 1;
// Descriptor type (code or data)
entry->s = 1;
// Granularity (enabled with 4KBytes increment)
entry->g = 1;
}
struct gdt_entry {
u32 limit_0_15 : 16;
u32 base_0_15 : 16;
u32 base_16_23 : 8;
u32 type : 4;
u32 s : 1;
u32 dpl : 2;
u32 p : 1;
u32 limit_16_19 : 4;
u32 avl : 1;
u32 l : 1;
u32 db : 1;
u32 g : 1;
u32 base_24_31 : 8;
} __attribute__((packed));
struct gdt_r {
u16 limit;
u32 base;
} __attribute__((packed));
struct gdt_entry gdt[6];
void gdt_init() {
// Null segment
struct gdt_entry null_entry = { 0 };
gdt[0] = null_entry;
// Kernel code segment
gdt_entry_init(gdt + 1, 0x0, 0xFFFFFFFF, SEG_TYPE_CER, SEG_AC_KERNEL);
// Kernel data segment
gdt_entry_init(gdt + 2, 0x0, 0xFFFFFFFF, SEG_TYPE_DRW, SEG_AC_KERNEL);
// User code segment
gdt_entry_init(gdt + 3, 0x0, 0x0, SEG_TYPE_CEO, SEG_AC_USER);
// User data segment
gdt_entry_init(gdt + 4, 0x0, 0x0, SEG_TYPE_DRO, SEG_AC_USER);
// TSS
gdt[5] = null_entry;
struct gdt_r gdtr;
gdtr.base = (u32)gdt;
gdtr.limit = sizeof(gdt);
asm volatile("lgdt %0\n"
: /* no output */
: "m" (gdtr)
: "memory");
// 0x10 is the address of the the kernel data segment
asm volatile("movw 0x10, %%ax\n":);
asm volatile("movw %%ax, %%ds\n":);
asm volatile("movw %%ax, %%fs\n":);
asm volatile("movw %%ax, %%gs\n":);
asm volatile("movw %%ax, %%ss\n":);
// 0x8 is the address of the kernel code segment
asm volatile("pushl 0x8\n"
"pushl $1f\n"
"lret\n"
"1:\n"
: /* no output */);
}
If you guys have any idea whats going on with this.
It turned out a really smart person find the issues:
I missed the $ in front of direct value when writing inline asm:
// 0x10 is the address of the the kernel data segment
asm volatile("movw $0x10, %%ax\n":);
asm volatile("movw %%ax, %%ds\n":);
asm volatile("movw %%ax, %%fs\n":);
asm volatile("movw %%ax, %%gs\n":);
asm volatile("movw %%ax, %%ss\n":);
// 0x8 is the address of the kernel code segment
asm volatile("pushl $0x8\n"
"pushl $1f\n"
"lret\n"
"1:\n"
: /* no output */);
I exchanged permissions for KERNEL and USER, correct should be
enum SEG_AC {
SEG_AC_KERNEL = 0b00,
SEG_AC_USER = 0b11,
};

usb crc5 11 bit check function

I have the following data from which I want to build the crc5.
address: 0x19, endp: 0x1, and crc 0x19.
value1 = convert_lsb(0x19) >> 1;
value2 = convert_lsb(0x1);
crc = crc5_11bit_usb(value1 << 4 | (value2 >> 4));
The crc5 result regarding the tool with addr 0x19 and endp 0x1 should be 0x19.
What is wrong with the code?
If I do the same with similiar data but the endp is 0xa the result is correct.
value1 = convert_lsb(0x3a) >> 1;
value2 = (convert_lsb(0xa));
crc = crc5_11bit_usb(value1 << 4 | (value2 >> 4));
The data comes from a tool to analyse usb and I want to build the crc5 manually.
Example code on
https://godbolt.org/z/Wqv1Mxnxj
The data are read from left to right therefore the endpoint is in front of the address and not at the end.

Adding bits at specific indexes for a uint8_t block

So I have a pointer to a uint8_t array in the form of:
uint8_t* array; // 64-bit array
I need to modify this array by shifting bits to the right and inserting a bit of 0 or 1 at indexes with a power of 2. Thereby generating a 72-bit array.
uint8_t newArr[9];
What is the best way to modify an array so I can add the bits at the specific places I computed. I thought of converting the array to a a char array and then adding the bits one by one. However, is there a faster and more easier method than this.
So if I have a pointer to a bit array in the form of uint8_t like:
000100001 11001111 01101101 11000001 11100000 00101111 11111001 10010010
I would need to modify it into a uint8_t[9] array so that I insert bits I have specified at 0, 1, 2, 4, 8, 16, 32, 64 of the new array to look like: (answer is wrong)
00000000 11001111 11001111 11001111 11001111 01101101 01101101 10010010 00100001
But I don't know how to shift a particular bit to the right without shifting all the bits. For example if I shift all bits starting at index 2 1 to the right and then all bits starting from index 4 to the right by one.
Say you start with the following:
src[0] src[1] src[2] src[3] src[4] src[5] src[6] src[7]
-------- -------- -------- -------- -------- -------- -------- --------
7 6 5 4 3 2 1
76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210
aaaaaaaa bbbbbbbb cccccccc dddddddd eeeeeeee ffffffff gggggggg hhhhhhhh
You want to insert at the following positions (octal):
0, 1, 2, 4, 10, 20, 40, 100
That means you want the following:
dst[0] dst[1] dst[2] dst[3] dst[4] dst[5] dst[6] dst[7] dst[8]
-------- -------- -------- -------- -------- -------- -------- -------- --------
1
0 7 6 5 4 3 2 1
76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210
aaaaaaa0 abbbbbbb bccccccc cddddddd deeeeee0 eeffffff ffggggg0 ggghhhh0 hhh0h000
So,
dst[0] = src[0] & 0xFE;
dst[1] = ((src[0] ) << 7) | ((src[1] ) >> 1);
dst[2] = ((src[1] ) << 7) | ((src[2] ) >> 1);
dst[3] = ((src[2] ) << 7) | ((src[3] ) >> 1);
dst[4] = ((src[3] ) << 7) | ((src[4] & 0xFC) >> 1);
dst[5] = ((src[4] ) << 6) | ((src[5] ) >> 2);
dst[6] = ((src[5] ) << 6) | ((src[6] & 0xFC) >> 2);
dst[7] = ((src[6] ) << 5) | ((src[7] & 0xFC) >> 3);
dst[8] = ((src[7] & 0x0E) << 5) | ((src[7] & 0x01) << 3);
(Useless masks omitted.)
You can treat the input array as a 64-bit int to make it faster. Suppose the input and output values are like this
array = aaaaaaaa bbbbbbbb cccccccc dddddddd eeeeeeee ffffffff gggggggg hhhhhhhh
newArr = aaaaaaa0 abbbbbbb bccccccc cddddddd deeeeee0 eeffffff ffggggg0 ggghhhh0 hhh0h000
Then you can get the desired result using this way
uin64_t src = htobe64(*(uint64_t*)array); // operate on big endian
uint64_t* dst = (uint64_t*)newArr;
*dst = (src & 0xFE00'0000'0000'0000) >> 0; // aaaaaaa
*dst |= (src & 0x01FF'FFFF'FC00'0000) >> 1; // abbbbbbbbccccccccddddddddeeeeee
*dst |= (src & 0x0000'0000'03FF'F800) >> 2; // eeffffffffggggg
*dst |= (src & 0x0000'0000'0000'07F0) >> 3; // ggghhhh
*dst = be64toh(*dst); // convert back to the native endian
newArr[8] = ((array[7] & 0x0E) << 4) | ((array[7] & 0x01) << 3); // hhh0h000
To avoid strict aliasing you can change to memcpy to copy from array to src and from dst to newArr
Of course you may need to align the input and output arrays for better performance. On many modern architectures it's also a must
#ifdef _MSC_VER
__declspec(align(8)) uint8_t array[8]; // 64-bit array
__declspec(align(8)) uint8_t newArr[9];
#else
uint8_t array[8] __attribute__((aligned(8))); // 64-bit array
uint8_t newArr[9] __attribute__((aligned(8)));
#endif
On modern x86 with BMI2 you can use the new bit deposit instruction to get the first 8 bytes directly
uint64_t src;
memcpy(&src, &array, sizeof src); // avoid strict aliasing
uin64_t src = htobe64(src);
uint64_t dst = _pdep_u64(src >> 4,
// aaaaaaa0 abbbbbbb bccccccc cddddddd deeeeee0 eeffffff ffggggg0 ggghhhh0
0b11111110'11111111'11111111'11111111'11111110'11111111'11111110'11111110);
// hhh0h000
newArr[8] = _pdep_u32(src, 0b11101000);
*dst = be64toh(*dst); // convert back to the native endian
memcpy(&newArr, &dst, sizeof dst); // avoid strict aliasing

AVR Atmega168 I2C LCD does not want to initialize

I'm using I2C converter to send data to my lcd.
The converter is based on PCF85741, and the lcd is Hitachi hd44780.
Port mapping between PCF85741 and lcd is as follows:
P0 -> RS
P1 -> RW
P2 -> E
P3 -> ?
P4 -> D4
P5 -> D5
P6 -> D6
P7 -> D7
The documentation says that the default address of my slave is 0x20, but with RW bit I need to send 0x40.
Here is my code:
void twi_start()
{
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTA);
while (!(TWCR & (1 << TWINT)));
}
void twi_stop()
{
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
while (!(TWCR & (1 << TWSTO)));
}
void twi_write(uint8_t byte)
{
TWDR = byte;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
}
void twi_write_byte(uint8_t byte)
{
uint8_t SLAVE_ADDRESS = 0x40;
twi_start();
twi_write(SLAVE_ADDRESS);
twi_write(byte);
twi_stop();
}
Lcd init
void lcd_init2()
{
for (int i = 0; i < 3; i++) {
twi_write_byte(0x03);
_delay_ms(20);
}
twi_write_byte(0x02);
_delay_ms(20);
//4 bit mode
twi_write_byte(0x24); // D5 -> 1, E -> 1
_delay_ms(10);
twi_write_byte(0x20); // D5 -> 1, E -> 0
_delay_ms(10);
//2 lines
twi_write_byte(0x24); // D5 -> 1, E -> 1
_delay_ms(10);
twi_write_byte(0x20); // D5 -> 1, E -> 0 first nibble
_delay_ms(10);
twi_write_byte(0x84); // D7 -> 1, E -> 1
_delay_ms(10);
twi_write_byte(0x80); // D7 -> 1, E -> 0 second nibble
_delay_ms(10);
}
After this code, the lcd should be in 4bit mode, with 2 lines, but it isn't
Nothing changes on lcd.
1) Please clarify what the I2C-to-parallel IC you have? I cannot find what is PCF85741, I see datasheets only for PCF8574 and PCF8574A.
In the first case, the slave address will be (including r/w bit) 0x40...0x4F, in the second - 0x70...0x7F.
Speaking from my experience the usual I2C circuit which comes with those displays has PCF8574A on it (i.e. address is 0x7*). And pin 3 there, by the way, is used to control the backlight.
2) Make sure what lower bits of the address you have? Are inputs A0 A1 A2 pulled up or tied to ground?
Again, from my experience, those boards usually have pull-ups to +5, and solderable jumpers on the board, to short those to ground. By default, the jumpers are not soldered, therefore A0 A1 A2 has a high logical level, thus the I2C address of the device is 0x7E (writing)/0x7F (reading). If you have PCF8574T on board, then the address will be 0x4E/0x4F
You can easily detect has the IC answered or not, by checking TWS bits in TWSR register (TWSR & TW_STATUS_MASK) should be equal to TW_MT_SLA_ACK (0x18) after the address is transmitted, or TW_MT_DATA_ACK (0x28) after the data byte is transmitted. (See the datasheet section 19.8.1 Master Transmitter Mode pages 186-188)
Or, more simply, if you have P3 of the PCF8574 connected to the backlight, you can try output 0x08 / 0x00 to see if the backlight is turning on and off.
3) For the initialization sequence look at Figure 24 4-Bit Interface on page 46 of HD44780 datasheet
Note bits DB5 and DB4 are high. Also, be careful, since you're only writing to the display controller, the bit R/W (i.e. the bit 1 of the output) should always be zero (note you're sending wi_write_byte(0x03); and then twi_write_byte(0x02); with the bit 1 is set high).
The example may be as follows:
void send4bits(uint8_t fourbits, bool is_cmd) {
uint8_t d = (fourbits << 4) | 0b1000;
if (!is_cmd) d |= 1;
twi_write_byte(d | 0b100); // E high
twi_write_byte(d); // E low
}
void sendcmd(uint8_t cmd) {
send4bits(cmd >> 4, true);
send4bits(cmd & 0xF, true);
}
void senddata(uint8_t cmd) {
send4bits(cmd >> 4, false);
send4bits(cmd & 0xF, false);
}
// initialization sequence
send4bits(0b0011, true);
_delay_ms(5);
send4bits(0b0011, true);
_delay_ms(1);
send4bits(0b0011, true);
// since I2C is slow enough the required 100us pause already happened here
send4bits(0b0010, true);
sendcmd(0b00101000);
sendcmd(0b00001000);
sendcmd(0b00000001);
delay_ms(2);
sendcmd(0b00000110);
sendcmd(0b00001110);
// Initialization is done
sendcmd(0x80); // Set cursor at the beginning
for (uint8_t i = 'A' ; i <= 'Z' ; i++) {
senddata(i); // Send some random data
}

Assigning individual bits to bytes

I have a to make a SPI communication between a microcontroller and another chip. The chip accepts a 16bit word. But the abstraction library requires the data to be sent as two 8bit bytes. Now I want to make a wrapper so I can easily create requests for read and write...but I have not yet got any success. Here is how it supposed to be:
The table below shows 16bits. The MSB can be 0 for write or 1 for read. The address can be from 0x0 to 0x7 and the data is 11 bits.
R/W | ADDRESS | DATA
B15 | B14-B11 | B10-B0
0 | 0000 | 00000000000
W0 | A3, A2, A1, A0 | D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0
For example, if I want to read from register 0x1 I think I have to set the bits like this:
W0 | A3, A2, A1, A0 | D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0
1 | 0 0 0 1 | 0 0 0 0 0 0 0 0 0 0 0
Or reading from register 0x7:
W0 | A3, A2, A1, A0 | D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0
1 | 0 1 1 1 | 0 0 0 0 0 0 0 0 0 0 0
I have tried to create this struct/union to see if it can work:
typedef struct{
uint8_t acc_mode:1;
uint8_t reg_addr:4;
uint8_t reg_data:8; //TODO fix me should be 11
} DRVStruct;
typedef union {
DRVStruct content;
uint16_t all;
} DRVUnion;
void DRV_PrepareReadMsg(uint8_t reg, uint8_t* msgBuffer) {
DRVUnion temp;
temp.content.acc_mode = 1;
temp.content.reg_addr = reg;
temp.content.reg_data = 0; //read mode does not need data!
msgBuffer[1] = temp.all & 0xFF;
msgBuffer[0] = temp.all >> 8;
}
I am getting strange results...from time to time I get answer from the SPI (I am sure the SPI communication is OK, but my code for preparing messages is the problem).
So the questions are:
Am I doing the right thing or approach?
How can I increase bit width of reg_data from 8 to 11 without getting compile error?
What do you suggest for a better approach?
This seems to work:
#include <stdio.h>
#include <stdint.h>
typedef union {
struct{ // no struct tag, since it is not needed...
uint16_t acc_mode:1;
uint16_t reg_addr:4;
uint16_t reg_data:11; //TODO fix me should be 11
} bits;
uint16_t all;
uint8_t bytes[2]; //extra bonus when lit;-)
} DRVUnion;
int main(void)
{
DRVUnion uni,uni13[13];
printf("Size=%zu, %zu\n", sizeof uni, sizeof uni13);
return 0;
}

Resources