Answer 1/3: use a union and a packed struct
See also my much longer answer here: Portability of using union for conversion
You can do the conversion to a byte array using a union. Be sure to pack the struct to remove padding bytes.
typedef struct __attribute__ ((__packed__)) message_s
{
    uint16_t time;
    uint16_t lat;
    uint8_t ns;
    uint16_t lon;
    uint8_t ew;
} message_t;
typedef union message_converter_u
{
    message_t message;
    uint8_t bytes[sizeof(message_t)];
} message_converter_t;
Now do the conversion through the message_converter_t union:
message_t message =
{
    .time = 0x1234,
    .lat = 0x2122,
    .ns = 'n',  // 0x6E
    .lon = 0x1834,
    .ew = 'e', // 0x65
};
message_converter_t converter;
converter.message = message;
That's it!
converter.bytes is now magically a uint8_t array with 8 elements containing all the bytes of the struct.
It has endianness considerations, however!
Note: copying message into converter.message above is unnecessarily inefficient, since it's an unnecessary byte-for-byte copy of the whole message struct. A more-efficient way is to simply construct the union type alone and populate the struct data inside the union directly. See "struct_to_array_via_type_punning_union_more_efficient.c" below for that demo.
Here is some sample print code to print all the bytes:
// Print the bytes
printf("bytes = [");
for (size_t i = 0; i < sizeof(converter.bytes); i++)
{
    printf("0x%02X", converter.bytes[i]);
    if (i < sizeof(converter.bytes) - 1)
    {
        printf(", ");
    }
}
printf("]\n");
and the output on a 64-bit little-endian x86-architecture Linux machine:
bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]
Notice that due to my machine being little-endian, the least-significant-byte 0x34 comes first in the time variable of 0x1234. So, you get 0x34 and then 0x12. This happens with all of the multi-byte variables. To remove endianness considerations across hardware architectures, you'd have to move to a bit-shifting approach instead of using a union--see my link above for examples and more details, and also see my Answer 2/3 here.
Full, runnable example
struct_to_array_via_type_punning_union.c: <-- download it as part of my eRCaGuy_hello_world repo
#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`
typedef struct message_unpacked_s
{
    uint16_t time;
    uint16_t lat;
    uint8_t ns;
    uint16_t lon;
    uint8_t ew;
} message_unpacked_t;
typedef struct __attribute__ ((__packed__)) message_s
{
    uint16_t time;
    uint16_t lat;
    uint8_t ns;
    uint16_t lon;
    uint8_t ew;
} message_t;
typedef union message_converter_u
{
    message_t message;
    uint8_t bytes[sizeof(message_t)];
} message_converter_t;
// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("This is the start of `main()`.\n");
    // demonstrate that packing the struct matters
    printf("sizeof(message_unpacked_t) = %zu bytes\n", sizeof(message_unpacked_t)); // 10 bytes due to padding
    printf("sizeof(message_t) = %zu bytes\n", sizeof(message_t)); // 8 bytes
    message_t message =
    {
        .time = 0x1234,
        .lat = 0x2122,
        .ns = 'n',  // 0x6E
        .lon = 0x1834,
        .ew = 'e', // 0x65
    };
    message_converter_t converter;
    // Note: copying `message` into `converter.message` here is unnecessarily inefficient. A
    // more-efficient way is to simply construct the union type alone and populate the struct
    // data inside the union directly. See "struct_to_array_via_type_punning_union_more_efficient.c"
    // for that demo.
    converter.message = message;
    // Print the bytes
    printf("bytes = [");
    for (size_t i = 0; i < sizeof(converter.bytes); i++)
    {
        printf("0x%02X", converter.bytes[i]);
        if (i < sizeof(converter.bytes) - 1)
        {
            printf(", ");
        }
    }
    printf("]\n");
    return 0;
}
Build and run command:
mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj struct_to_array_via_type_punning_union.c \
-o bin/struct_to_array_via_type_punning_union && bin/struct_to_array_via_type_punning_union
Sample output:
eRCaGuy_hello_world/c$ mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj struct_to_array_via_type_punning_union.c \
>     -o bin/struct_to_array_via_type_punning_union && bin/struct_to_array_via_type_punning_union
This is the start of `main()`.
sizeof(message_unpacked_t) = 10 bytes
sizeof(message_t) = 8 bytes
bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]
More-efficient technique: do NOT copy from a struct to a union! Just use the union alone as your message_t!
struct_to_array_via_type_punning_union_more_efficient.c
#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`
typedef struct message_data_unpacked_s
{
    uint16_t time;
    uint16_t lat;
    uint8_t ns;
    uint16_t lon;
    uint8_t ew;
} message_data_unpacked_t;
typedef struct __attribute__ ((__packed__)) message_data_s
{
    uint16_t time;
    uint16_t lat;
    uint8_t ns;
    uint16_t lon;
    uint8_t ew;
} message_data_t;
typedef union message_u
{
    message_data_t data;
    uint8_t bytes[sizeof(message_data_t)];
} message_t;
// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("This is the start of `main()`.\n");
    // demonstrate that packing the struct matters
    printf("sizeof(message_data_unpacked_t) = %zu bytes\n", sizeof(message_data_unpacked_t)); // 10 bytes due to padding
    printf("sizeof(message_data_t) = %zu bytes\n", sizeof(message_data_t)); // 8 bytes
    message_t message =
    {
        .data =
        {
            .time = 0x1234,
            .lat = 0x2122,
            .ns = 'n',  // 0x6E
            .lon = 0x1834,
            .ew = 'e', // 0x65
        },
    };
    // Print the bytes
    printf("bytes = [");
    for (size_t i = 0; i < sizeof(message.bytes); i++)
    {
        printf("0x%02X", message.bytes[i]);
        if (i < sizeof(message.bytes) - 1)
        {
            printf(", ");
        }
    }
    printf("]\n");
    return 0;
}
Sample output is the same as before:
eRCaGuy_hello_world/c$ mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj struct_to_array_via_type_punning_union_more_efficient.c \
>     -o bin/struct_to_array_via_type_punning_union_more_efficient && bin/struct_to_array_via_type_punning_union_more_efficient
This is the start of `main()`.
sizeof(message_data_unpacked_t) = 10 bytes
sizeof(message_data_t) = 8 bytes
bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]
Related
Here is one of my kind of related answers in C++, which I want to remember in case I need to look at it again: Use a char[4] or char[8] as a constant int?
Keywords: c type punning; c struct to byte array conversion; c array to bytes; c serialization; c struct serialization; c object serialization