You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

965 lines
22 KiB

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "serial.h"
#include "a3kpacket.h"
#include "gpio.h"
#include "a3k.h"
#include "tick.h"
#define WAIT_TIMEOUT (100) /* 100ms */
#define RATE_PINS (6)
struct fifo_tag {
char *pnew; /* points to newest fifo element */
char *pold; /* points to oldest fifo element */
short nelem;/* number of bytes currently in fifo */
short blen;
char *bend;
char *b;
};
typedef struct fifo_tag FIFO;
struct a3k_tag {
struct serial *uart;
uint32_t rst;
uint32_t rts;
int pseudo;
uint32_t rate[RATE_PINS];
uint32_t ec;
uint32_t ns;
uint32_t es;
FIFO rx;
uint8_t _buf[FORMAT3KBLEN];
};
static char *put_next_byte( char *p, short val )
{
*p++ = val;
return p;
}
static char *put_next_word( char *p, short val )
{
*p++ = (val >> 8) & 0x00ff;
*p++ = val & 0x00ff;
return p;
}
static char *get_next_byte( char *p, short *val )
{
*val = *p++ & 0x00ff;
return p;
}
static char *get_next_word( char *p, short *val )
{
*val = *p++;
*val <<= 8;
*val |= *p++;
return p;
}
static void __send_all(a3k_t tag, const uint8_t *p, int len)
{
int n;
while (len > 0) {
n = serial_send_buffered(tag->uart, p, len);
p += n;
len -= n;
}
}
static void fifo_putpacket(a3k_t tag, char *p)
{
int len;
len = (((short)p[1])<<8)|p[2];
len += 4;
__send_all(tag, (void *)p, len);
}
#if 0
static char __getc(a3k_t a3k)
{
char c;
while (serial_recv_buffered(a3k->uart, (void *)&c, 1) != 1)
;
return c;
}
#endif
static char __fifo_getc(FIFO *f)
{
if (f->nelem) {
char c;
c = *f->pold++;
if(f->pold == f->bend)
f->pold = f->b;
f->nelem -= 1;
return c;
} else
return 0;
}
static int __getpacket(a3k_t tag, char *p)
{
short len;
short i;
uint8_t *p_save = (uint8_t *)p;
FIFO *f = &tag->rx;
p[0] = __fifo_getc(f); //get the header
p[1] = __fifo_getc(f); //get the len (msb)
p[2] = __fifo_getc(f); //get the len (lsb)
p[3] = __fifo_getc(f); //get the type
len = p[1];
len <<= 8;
len |= (p[2] & 0x00ff);
p += 4;
for ( i = 0; i < len; i++ ) {
*p++ = __fifo_getc(f);
}
printf("\r\n");
for (i = 0; i < (len + 4); ++i) {
printf("%02x ", p_save[i]);
}
printf("\r\n");
return len + 4;
}
static void put_packet(a3k_t a3k, char *packet, char *end, char type, char parity)
{
char *p = packet;
short len = end - packet - 4;
if (parity)
len += 2;
/*
* add the header, length, and type
*/
p = put_next_byte( p, PKT_HEADER );
p = put_next_word( p, len );
p = put_next_byte( p, type );
/*
* from this point on, len is the total length of the
* packet including header/len/type
*/
len += 4;
/*
* add the parity field if enabled. Note that the parity
* byte does not cover the header byte.
*/
if (parity) {
short i;
char pbyte = 0;
end = put_next_byte(end, PKT_PARITYBYTE );
for ( i = 1; i < len - 1; i++ ) {
pbyte ^= packet[i];
}
end = put_next_byte(end, pbyte );
}
fifo_putpacket(a3k, packet);
}
/*
* This function gets the next character from a fifo. It is assumed
* that the fifo contains at leas one character. If it does not the
* function returns a null character.
*/
static int __fifo_waitpacket(a3k_t tag)
{
int n, len;
char *old;
FIFO *f = &tag->rx;
uint64_t end = millis() + WAIT_TIMEOUT;
restart:
if (millis() > end) {
return -1;
}
/* try to receive from serial */
while (f->blen != f->nelem) {
len = f->bend - f->pnew;
n = serial_recv_buffered(tag->uart, (void *)f->pnew, len);
if (n > 0) {
f->pnew += n;
if (f->pnew == f->bend) {
f->pnew = f->b;
}
f->nelem += n;
} else {
break;
}
}
while ((f->nelem > 0) && (*f->pold != PKT_HEADER)) {
__fifo_getc(f);
}
if (*f->pold != PKT_HEADER)
goto restart;
if (f->nelem < 3)
goto restart;
old = f->pold + 1;
if (old == f->bend) {
old = f->b;
}
len = *old++;
len <<= 8;
if (old == f->bend) {
old = f->b;
}
len |= *old++;
if (f->nelem < (len + 4)) {
goto restart;
}
return 0;
}
static int get_packet(a3k_t a3k, char *packet)
{
#if 0
return __getpacket(a3k, packet);
#else
if (__fifo_waitpacket(a3k)) {
return 0;
}
return __getpacket(a3k, packet);
#endif
}
static int wait_a3k_ready(a3k_t tag)
{
char packet[30];
memset(packet, 0, sizeof packet);
get_packet(tag, packet);
if (packet[0] != 0x61)
return (1);
if (packet[1] != 0x0)
return (2);
if (packet[2] != 0x03)
return (3);
if(packet[3] != 0x0)
return (4);
if(packet[4] != 0x39)
return (5);
if(packet[5] != 0x2f)
return (6);
if (packet[6] != (0x3^0x39^0x2f))
return (7);
/* we should verify that it is a ready packet, but for now we do not */
return (0);
}
int get_cfg(a3k_t a3k, short * cfg0, short * cfg1, short * cfg2)
{
char packet[30];
char *p = packet+4;
short plen;
short chk_header, chk_len, chk_type, chk_field;
p = put_next_byte(p,PKT_GETCFG);
put_packet(a3k, packet, p, PKT_CONTROL, 1);
plen = get_packet(a3k,packet);
if(plen == 0)
return 1;
p = packet;
p = get_next_byte(p, &chk_header);
if(chk_header != PKT_HEADER)
return -1;
p = get_next_word(p, &chk_len);
if(chk_len != 6)
return -2;
p = get_next_byte(p,&chk_type);
if(chk_type != PKT_CONTROL)
return -3;
p = get_next_byte(p, &chk_field);
if(chk_field != PKT_GETCFG)
return -4;
p = get_next_byte(p, cfg0);
p = get_next_byte(p, cfg1);
p = get_next_byte(p, cfg2);
return 0;
}
static int packet_reset_a3k(a3k_t a3k)
{
uint64_t end = millis() + 5;
gpio_clear(a3k->rst);
while (millis() < end)
;
serial_rx_buffer_clear(a3k->uart);
serial_tx_buffer_clear(a3k->uart);
gpio_set(a3k->rst);
wait_a3k_ready(a3k);
#if 1
char packet[30];
char *p = packet+4;
short cfg = (1<<13); //noise suppressor on
// short cfg0, cfg1, cfg2;
p = put_next_byte(p, PKT_RESETSOFTCFG );
cfg |= (5<<8); /* UART */
p = put_next_byte( p, 0x05); //DTX_ENABLE = 0, IF_SELECT0 = 1, IF_SELECT1 = 0, IF_SELECT2 = 1;
p = put_next_byte( p, 0);
p = put_next_byte( p, 0);
p = put_next_byte( p, 0x0D); //DTX_ENABLE = 0, IF_SELECT0 = 1, IF_SELECT1 = 0, IF_SELECT2 = 1;
p = put_next_byte( p, 0x00);
p = put_next_byte( p, 0x00);
put_packet(a3k, packet, p, PKT_CONTROL, 1);
return wait_a3k_ready(a3k);
// get_cfg(a3k, &cfg0, &cfg1, &cfg2);
#endif
}
static int config_a3k(a3k_t interface, short ratet, char input_gain, char output_gain)
{
/*
* we can modify this to put characters into a fifo instead of
* a buffer. We would then modify put_packet and get_packet
* to work with a fifo instead of a buffer. This will save
* stack space and save code. we would need to create
* fifo_putword() which puts two characters.
*/
char packet[30];
char *p = packet+4;
short chk_header, chk_len, chk_type, chk_field, chk_response_ok;
short plen;
short lowpower = 0;
if (ratet >= 0) {
p = put_next_byte( p, PKT_RATET );
p = put_next_byte( p, ratet );
}
if (input_gain != 0 || output_gain != 0) {
p = put_next_byte(p, PKT_GAIN);
p = put_next_byte(p, input_gain);
p = put_next_byte(p, output_gain);
}
p = put_next_byte( p, PKT_LOWPOWER );
p = put_next_byte( p, lowpower );
p = put_next_byte( p, PKT_INIT );
p = put_next_byte( p, PKT_INIT_ENCODER|PKT_INIT_DECODER );
put_packet(interface, packet, p, PKT_CONTROL, 1);
//we expect a 9 byte response packet
plen = get_packet(interface, packet);
if (plen == 0) {
return 1;
}
p = packet;
p = get_next_byte( p, &chk_header );
if (chk_header != PKT_HEADER)
return -1;
p = get_next_word( p, &chk_len );
if (ratet >= 0) {
if (input_gain != 0 || output_gain != 0) {
if (chk_len != 6 + 4)
return -2;
} else {
if (chk_len != 6+2)
return -2;
}
} else {
if (input_gain != 0 || output_gain != 0) {
if (chk_len != (6 + 2))
return -2;
} else {
if (chk_len != 6)
return -2;
}
}
p = get_next_byte( p, &chk_type );
if (chk_type != PKT_CONTROL)
return -3;
if (ratet >= 0) {
p = get_next_byte( p, &chk_field);
if (chk_field != PKT_RATET)
return -4;
p = get_next_byte( p, &chk_response_ok);
if (chk_response_ok != PKT_RESPONSE_OK)
return -5;
}
if (input_gain != 0 || output_gain != 0) {
p = get_next_byte(p, &chk_field);
if (chk_field != PKT_GAIN) {
return -6;
}
p = get_next_byte(p, &chk_response_ok);
if (chk_response_ok != PKT_RESPONSE_OK)
return -7;
}
p = get_next_byte( p, &chk_field);
if (chk_field != PKT_LOWPOWER)
return -8;
p = get_next_byte( p, &chk_response_ok);
if (chk_response_ok != PKT_RESPONSE_OK)
return -9;
p = get_next_byte( p, &chk_field);
if (chk_field != PKT_INIT)
return -10;
p = get_next_byte( p, &chk_response_ok);
if (chk_response_ok != PKT_RESPONSE_OK)
return -11;
return 0;
}
static int config_a3k_codec(a3k_t interface )
{
char packet[128];
char *p = packet+4;
short plen;
short INPUT_GAIN;
short INPUT_SEL;
short ADCGAIN, DACGAIN;
INPUT_GAIN = 0x3;
// INPUT_GAIN = 0x3b; // mute,24db
INPUT_SEL = 0x2;
ADCGAIN = 0x3e; //20 dB
// ADCGAIN = 0x2A; //0 dB
DACGAIN = 0x7e; //20db
// DACGAIN = 0x74; //0db
#if 0
/*
* there is two ways to set up the codec. This is the first
* method. Use PKT_CODECCFG to specify what control registers
* to change and the new values. Then later they will be
* sent to the codec when the PKT_CODECSTART is sent.
*/
p = put_next_byte( p, PKT_CODECCFG );
p = put_next_byte( p, 6 );
p = put_next_word (p, (2<<8)| (1<<7)|(4<<3)); // set TURBO=1 (SCLK=MCLK/P), keep I2C addr=4
p = put_next_word (p, (1<<8)| 1|(1<<6) /*|(1<<5)*/ ); // set 16 bit DAC mode, set continuous data transfer mode
p = put_next_word (p, (4<<8)| 0x80|3); // set M=3
p = put_next_word (p, (5<<8)| ADCGAIN );
p = put_next_word (p, (5<<8)| 0x80|(7<<3)| INPUT_GAIN); // sidetone=MUTE / INPUT GAIN = 24dB ==> 0x3
p = put_next_word (p, (6<<8)| (INPUT_SEL<<1)); // set input LINE IN = 0x6 MIC = 0x2
#else
/*
* For the second method, use PKT_CODECCFG to specify that 0
* registers are configured by PKT_CODECSTART. Then use
* PKT_SETCODECRESET to take the codec out of reset. Use
* PKT_DELAYNUS to insert delay, then use PKT_WRITEI2C
* repeatedly to generate i2c writes. This sets up the AIC14,
* later when PKT_CODECSTART is received, it will not try to
* reconfigure the aic14. All it does is enable the codec
* interface of the a3k without configuring the aic14 first.
* Either method works. This method is more generic.
*/
p = put_next_byte( p, PKT_CODECCFG );
p = put_next_byte( p, 0 );
p = put_next_byte( p, PKT_SETCODECRESET );
p = put_next_byte( p, PKT_DELAYNUS );
p = put_next_word( p, 10 );
#define PKT_WRITEREG( p, reg, val ) p = put_next_byte( p, PKT_WRITEI2C ); p = put_next_byte( p, 3 ); p = put_next_byte( p, 0x80 ); p = put_next_byte( p, reg ); p = put_next_byte( p, val )
PKT_WRITEREG (p, 2, (1<<7)|(4<<3)); // set TURBO=1 (SCLK=MCLK/P), keep I2C addr=4
PKT_WRITEREG (p, 1, 1|(1<<6) /*|(1<<5)*/); // set 16 bit DAC mode, set continuous data transfer mode
PKT_WRITEREG (p, 4, 0x8A);
PKT_WRITEREG (p, 4, (0x10));
PKT_WRITEREG (p, 5, ADCGAIN);
PKT_WRITEREG (p, 5, DACGAIN);
PKT_WRITEREG (p, 5, 0x80|(7<<3)| INPUT_GAIN); // sidetone=MUTE / INPUT GAIN = 24dB ==> 0x3
PKT_WRITEREG (p, 6, (INPUT_SEL<<1)); // set input LINE IN = 0x6 MIC = 0x2
#endif
p = put_next_byte( p, PKT_DISCARDNCODEC );
p = put_next_word( p, 128 ); //tell the a3k to discard the first 128 codec samples, output 0
//final delay will delay response packet which puts some time
//between config_a3k_codec and start_a3k_codec.
p = put_next_byte( p, PKT_DELAYNUS );
p = put_next_word( p, 10 );
put_packet(interface, packet, p, PKT_CONTROL, 1);
//we expect a 9 byte response packet
plen = get_packet(interface, packet);
if (plen == 0)
return 1;
return 0;
}
static int start_a3k_codec(a3k_t interface, short skew, short passthru, short codecif)
{
char packet[30];
char *p = packet+4;
short chk_header, chk_len, chk_type, chk_field, chk_response_ok;
short plen;
p = put_next_byte( p, PKT_STARTCODEC );
p = put_next_byte( p, (skew & 1) | ((passthru & 1)<<1) | ((codecif & 1)<<2) );
put_packet(interface, packet, p, PKT_CONTROL, 1);
plen = get_packet(interface, packet);
if (plen == 0)
return 1;
p = packet;
p = get_next_byte( p, &chk_header );
if (chk_header != PKT_HEADER)
return -1;
p = get_next_word( p, &chk_len );
if (chk_len != 4)
return -2;
p = get_next_byte( p, &chk_type );
if (chk_type != PKT_CONTROL)
return -3;
p = get_next_byte( p, &chk_field);
if (chk_field != PKT_STARTCODEC)
return -12;
p = get_next_byte( p, &chk_response_ok);
if (chk_response_ok != PKT_RESPONSE_OK)
return -13;
return 0;
}
static int __attribute__((unused)) stop_a3k_codec(a3k_t interface)
{
char packet[30];
char *p = packet+4;
short chk_header, chk_len, chk_type, chk_field, chk_response_ok;
short plen;
p = put_next_byte( p, PKT_STOPCODEC );
put_packet(interface, packet, p, PKT_CONTROL, 1);
plen = get_packet(interface, packet);
if (plen == 0)
return 1;
p = packet;
p = get_next_byte( p, &chk_header );
if (chk_header != PKT_HEADER)
return -1;
p = get_next_word( p, &chk_len );
if (chk_len != 4)
return -2;
p = get_next_byte( p, &chk_type );
if (chk_type != PKT_CONTROL)
return -3;
p = get_next_byte( p, &chk_field);
if (chk_field != PKT_STARTCODEC)
return -12;
p = get_next_byte( p, &chk_response_ok);
if (chk_response_ok != PKT_RESPONSE_OK)
return -13;
return 0;
}
void pins_set(a3k_t tag, int rate, int ec, int ns, int es)
{
gpio_write(tag->rate[0], (0x01 & rate));
gpio_write(tag->rate[1], (0x02 & rate));
gpio_write(tag->rate[2], (0x04 & rate));
gpio_write(tag->rate[3], (0x08 & rate));
gpio_write(tag->rate[4], (0x10 & rate));
gpio_write(tag->rate[5], (0x20 & rate));
gpio_write(tag->ec, ec);
gpio_write(tag->ns, ns);
gpio_write(tag->es, es);
}
void read_cfg(a3k_t tag)
{
char packet[10];
char *p = packet + 4;
p = put_next_byte(p, 0x37);
put_packet(tag, packet, p, PKT_CONTROL, 1);
get_packet(tag, packet);
}
static void __fifo_init(FIFO *f, short blen, char *b)
{
f->pnew = f->pold = b;
f->blen = blen;
f->bend = b + blen;
f->b = b;
f->nelem = 0;
}
/*
* This function does not take anything out of the fifo. It assumes
* that there is a complete packet in the fifo (or at least the
* header/length/type). It just looks at the 4th byte which is the
* type byte and returns it. In the event that the fifo does not
* contain at least 4 bytes, it returns 0x0ff.
*/
int a3k_peekpackettype(a3k_t tag)
{
FIFO *f = &tag->rx;
char i;
char c;
volatile char *old = f->pold;
for ( i = 0; i < 4; i++ ) {
c = *old++;
if (old == f->bend)
old = f->b;
}
/*
* just return the forth byte. Note that nelem wasn't
* decremented and the fifo pointers are unchanged.
* This function peeked into the fifo without changing
* it.
*/
return c;
}
/*
* this function discards a packet from the fifo.
*/
void a3k_discardpacket(a3k_t tag)
{
FIFO *f = &tag->rx;
/*
* one we get len we can just subtract len+1 from elem and
* then do a circular wrap around of the fifo pointer.
* for now i discard characters one at a time.
*/
char c;
short len;
short i;
__fifo_getc(f);
c = __fifo_getc(f);
len = c;
len <<= 8;
c = __fifo_getc( f );
len |= (c & 0x00ff);
len++; //dont forget the type byte
for ( i = 0; i < len; i++ ) {
__fifo_getc( f );
}
}
int a3k_getpacket(a3k_t tag, uint8_t *buf, int size)
{
char c;
int i = 0, x, len;
FIFO *f = &tag->rx;
buf[i++] = __fifo_getc(f);
c = __fifo_getc(f);
buf[i++] = c;
len = (c << 8);
c = __fifo_getc(f);
buf[i++] = c;
len |= (c & 0xff);
++len; // dont forget the type byte
for (x = 0; x < len && x < (size - 4); ++x) {
buf[i++] = __fifo_getc(f);
}
while (x < len) { /* drop */
__fifo_getc(f);
++x;
}
return (len + 3);
}
int a3k_get_rawpacket(a3k_t tag, uint8_t *buf, int size)
{
uint8_t c;
int n = 0, len;
FIFO *f = &tag->rx;
__fifo_getc(f); /* drop 0x61 */
c = __fifo_getc(f); /* len (MSB) */
len = (c << 8) | (__fifo_getc(f) & 0xff); /* len (LSB) */
__fifo_getc(f); /* drop type */
c = __fifo_getc(f); /* drop CHAND field */
assert(c == 1);
c = __fifo_getc(f); /* CHANAD bits */
c /= 8; /* to bytes */
for (n = 0; n < c && n < size; ++n) {
buf[n] = __fifo_getc(f);
}
len -= n;
while (len > 0) { /* drop 0x2f, parity, etc ... */
__fifo_getc(f);
}
return (n);
}
int a3k_putpacket(a3k_t tag, const void *_ptr, int len)
{
__send_all(tag, _ptr, len);
return (len);
}
/*
* This function just checks the fifo without modifying it, unless it
* discovers that the header is missing. When the fifo is missing
* the header all data is discarded until a header is found.
*/
int a3k_checkfullpacket(a3k_t tag)
{
FIFO *f = &tag->rx;
int len, n;
char *old;
start:
/* try to receive from serial */
while (f->blen != f->nelem) {
len = f->bend - f->pnew;
n = serial_recv_buffered(tag->uart, (void *)f->pnew, len);
if (n > 0) {
f->pnew += n;
if (f->pnew == f->bend) {
f->pnew = f->b;
}
f->nelem += n;
} else {
break;
}
}
/*
* if there is a character in the fifo and it is not
* a header byte then discard it. Keep going as
* long as there are still bytes in the fifo. if a packet
* with bad parity is received it is discarded.
*/
while ((f->nelem > 0) && ( *f->pold != PKT_HEADER )) {
__fifo_getc(f);
}
/*
* now we are either out of characters in the fifo
* or the first character is a header byte. If we
* dont have at least the header byte and the two
* len bytes yet, then just return.
*/
if (f->nelem < 3)
return 0;
/*
* we now have the header and two len bytes in the
* fifo.
*/
old = f->pold + 1;
if (old == f->bend)
old = f->b;
len = *old++;
len <<= 8;
if (old == f->bend)
old = f->b;
len |= *old++;
if (len > 340) {
__fifo_getc(f);
goto start;
}
if (f->nelem >= len+4) {
/*
* we now have received a full packet. We will
* check its parity. if the parity is bad then
* we will discard the packet, and return 0.
* If the packet is good then we will return 1.
*/
short i;
char parity = 0;
parity ^= ((len >> 8) & 0x00ff);
parity ^= (len & 0x00ff);
if (old == f->bend)
old = f->b;
for(i=0;i<len+1;i++) {
parity ^= *old++;
if (old == f->bend)
old = f->b;
}
if (parity == 0)
return 1; /* we have a full packet with good parity check */
else {
a3k_discardpacket(tag);
return 0;
}
} else
return 0;
}
int a3k_setup(a3k_t tag, int baudrate)
{
int err = 0;
#ifdef STM32F429_439xx
int i;
#endif
__fifo_init(&tag->rx, FORMAT3KBLEN, (void *)tag->_buf);
if (baudrate < 0) {
baudrate = A3K_UART_BAUDRATE;
}
serial_setup(tag->uart, baudrate, 8, SERIAL_STOPBITS_1);
if (tag->pseudo) {
return 0;
}
#ifdef STM32F429_439xx
gpio_init(tag->rst, GPIO_OUTPUT | GPIO_FLAG_PP | GPIO_FLAG_PU);
gpio_init(tag->rts, GPIO_INPUT | GPIO_FLAG_PP | GPIO_FLAG_PU | GPIO_SPEED_FAST);
gpio_init(tag->ec, GPIO_OUTPUT);
gpio_init(tag->ns, GPIO_OUTPUT);
gpio_init(tag->es, GPIO_OUTPUT);
for (i = 0; i < RATE_PINS; ++i) {
gpio_init(tag->rate[i], GPIO_OUTPUT);
}
pins_set(tag, 0x2c, 0, 1, 0); //ec, ns, es
#endif
if (packet_reset_a3k(tag)) {
printf("reset a3k failed.\r\n");
err = -1;
goto tail;
}
/*
if (stop_a3k_codec(tag)) {
printf("stop codec failed.\r\n");
}
*/
if (config_a3k(tag, -1, 0, 0)) { /* ratet, input_gain, output_gain */
printf("config a3k failed.\r\n");
err = -2;
goto tail;
}
if (config_a3k_codec(tag)) {
printf("config a3k codec failed.\r\n");
err = -3;
goto tail;
}
// read_cfg(tag);
if (start_a3k_codec(tag, 0, 0, 0)) {
printf("start a3k codec mode failed.\r\n");
err = -4;
goto tail;
}
tail:
return err;
}
void a3k_close(a3k_t tag)
{
if (!tag->pseudo) {
packet_reset_a3k(tag);
}
serial_close(tag->uart);
}
#if (TARGET_HAS_A3K_0)
struct a3k_tag a3k0 = {
.uart = &serial1,
.rst = PF10,
.rts = PG7,
.pseudo = 0,
.rate = {PB0, PC4, PC5, PC3, PC0, PC1},
.ec = PC2,
.ns = PJ15,
.es = PG12,
};
#endif
#if TARGET_HAS_A3K_1
struct a3k_tag a3k1 = {
.uart = &serial2,
.rst = PI14,
.rts = PK2,
.pseudo = 0,
.rate = {PJ4, PJ13, PJ14, PK3, PG10, PK4},
.ec = PI15,
.ns = PJ12,
.es = PH4,
};
#endif
#if TARGET_HAS_A3K_2
struct a3k_tag a3k2 = {
.uart = &serial3,
.pseudo = 1,
};
#endif
#if TARGET_HAS_A3K_3
struct a3k_tag a3k3 = {
.uart = &serial5,
.pseudo = 1,
};
#endif