No nível mais simples, você poderia dizer que um protocolo de comunicação simples possui três camadas: físico, transporte e aplicativo. (Existem modelos com mais como OSI com 7 ou TCP / IP com 4. O número de camadas não é muito importante no contexto desta pergunta.)
A camada de aplicativo é a camada com a qual você lida diretamente no seu código e o foco da pergunta. No que diz respeito à camada de transporte, o byte que você passou em send_data é apenas um padrão binário, mas você pode interpretá-lo no código do aplicativo como a letra 'A'. O CRC ou o cálculo da soma de verificação será o mesmo, independentemente de você considerar o byte como 'A', 0x41 ou 0b01000001.
A camada de transporte é o nível do pacote, no qual você tem seus cabeçalhos de mensagens e a verificação de erros, seja CRC ou uma soma de verificação básica. No contexto do firmware, você pode ter uma função como send_data, onde passa um byte para enviar. Dentro dessa função, ele é inserido em um pacote que diz: "Ei, essa é uma mensagem normal, requer um reconhecimento e a soma de verificação é 0x47, a hora atual é X". Este pacote é enviado sobre a camada física para o nó receptor.
A camada física é onde os componentes eletrônicos e a interface são definidos: conectores, níveis de tensão, tempo, etc. Essa camada pode variar de alguns traços que executam sinais TTL para um UART básico em uma PCB, a um par diferencial totalmente isolado, como em alguns Implementações CAN .
No nó de recebimento, o pacote entra na camada física, é descompactado na camada de transporte e, em seguida, seu padrão binário está disponível para a camada de aplicativo. Cabe à camada de aplicação do nó receptor saber se esse padrão deve ser interpretado como 'A', 0x41 ou 0b01000001 e o que fazer com ele.
Em conclusão, é quase sempre aceitável enviar caracteres ASCII, se é isso que o aplicativo exige. O importante é entender seu esquema de comunicação e incluir um mecanismo de verificação de erros.