Page MenuHomec4science

sbcp.c
No OneTemporary

File Metadata

Created
Sun, Oct 6, 20:26
#include <sbcp-uc/sbcp.h>
#include <sbcp-uc/buffers.h>
#include <sbcp-uc/register.h>
#include <sbcp-uc/packet.h>
#ifdef SBCP_PROVIDE_MAIN
typedef enum sbcp_tx_state_t {
SBCP_TX_BUFFER_FREE = 0,
SBCP_TX_BUFFER_OCCUPIED ,
SBCP_TX_BUFFER_IN_TX
} sbcp_tx_state_t;
typedef enum sbcp_ll_flags_t {
SBCP_LL_NOTHING = 0,
SBCP_LL_RX_IS_AVAILABLE = 1 << 0,
SBCP_LL_TX_IS_AVAILABLE = 1 << 1,
SBCP_LL_IS_DISABLED = 1 << 2
} sbcp_ll_flags_t;
typedef enum sbcp_rx_state_t {
SBCP_NO_RX = 0,
SBCP_RX_WAIT_FOR_HEADER_BYTE,
SBCP_RX_WAIT_FOR_FULL_HEADER,
SBCP_RX_WAIT_FOR_FULL_PACKET,
SBCP_RX_OVERFLOW_PENDING
} sbcp_rx_state_t;
typedef enum sbcp_uart_err_flags_t {
SBCP_UART_ALL_ERRORS_DISABLED = 0x00,
SBCP_UART_FERR_ENABLED = 0x01,
SBCP_UART_PERR_ENABLED = 0x02,
SBCP_UART_OERR_ENABLED = 0x04
} sbcp_uart_err_flags_t;
struct sbcp_t {
gpio receive_disable;
gpio send_enable;
buffer_fifo rx_fifo;
buffer_fifo rx_ready_fifo;
sbcp_rx_state_t rx_state;
unsigned int rx_error;
volatile unsigned int * current_rx_packet;
sbcp_uart_err_flags_t bus_uart_error_flags;
sbcp_tx_state_t tx_state;
unsigned char is_for_us;
sbcp_ll_flags_t ll_flags;
unsigned char ll_payload[SBCP_LOW_LATENCY_IN_SIZE * 2];
unsigned char ll_previous_error_code;
sbcp_reg_address ll_in_registers[SBCP_LOW_LATENCY_IN_SIZE];
sbcp_reg_address ll_out_registers[SBCP_LOW_LATENCY_OUT_SIZE];
uint8_t ll_in_size;
uint8_t ll_out_size;
};
typedef struct sbcp_t sbcp_t;
#define uart_line_in_tx() do{ \
gpio_set(sbcp.receive_disable); \
gpio_set(sbcp.send_enable); \
}while(0)
#define uart_line_in_rx() do{ \
gpio_clear(sbcp.send_enable); \
gpio_clear(sbcp.receive_disable); \
}while(0)
#define dma_init_bus_rx() do { \
buffer_fifo_increment_tail(sbcp.rx_fifo); \
DMA0STA = buffer_fifo_get_dma_addr(sbcp.rx_fifo.tail); \
sbcp.current_rx_packet = buffer_fifo_get_cpu_addr(sbcp.rx_fifo.tail); \
DMA0CNT = 0; \
sbcp.rx_state = SBCP_RX_WAIT_FOR_HEADER_BYTE; \
DMA0CONbits.CHEN = 1;}while(0)
#define dma_bus_rx_end_header() do{ \
DMA0CONbits.CHEN = 0; \
DMA0STA += 2; \
DMA0CNT = 2; \
DMA0CONbits.CHEN = 1; \
sbcp.rx_state = SBCP_RX_WAIT_FOR_FULL_HEADER; \
}while(0)
#define SBCP_PACKET_SIZE_MASK (SBCP_PACKET_SIZE - 1)
#define dma_bus_rx_full_packet() do{ \
DMA0CONbits.CHEN = 0; \
DMA0STA +=6 ; \
sbcp_payload_size length = sbcp.current_rx_packet[SBCP_PAYLOAD_SIZE_POS]; \
DMA0CNT = (length & SBCP_PACKET_SIZE_MASK ) + 1; \
DMA0CONbits.CHEN = 1; \
sbcp.rx_state = SBCP_RX_WAIT_FOR_FULL_PACKET; \
}while(0)
//! Force GCC to not reorder the instruction before and after this macro
#define barrier() __asm__ __volatile__("": : :"memory")
#define dma_init_bus_tx(buffer) do{ \
DMA1STA = __builtin_dmaoffset( buffer ); \
DMA1CNT = (((volatile unsigned int) buffer[SBCP_PAYLOAD_SIZE_POS]) + 5 ) & SBCP_PACKET_SIZE_MASK; \
uart_line_in_tx(); \
DMA1CONbits.CHEN = 1; \
barrier(); \
DMA1REQbits.FORCE = 1; \
}while(0)
#define timer_4_start() do { \
T4CONbits.TON = 1; \
}while(0)
#define timer_4_stop() do{ \
T4CONbits.TON = 0; \
}while(0)
#define timer_4_stop_and_clear() do{ \
T4CONbits.TON = 0; \
TMR4 = 0x00; \
}while(0)
void init_timer_4(unsigned int frequency, unsigned long clock_frequency) {
timer_4_stop();
T4CONbits.T32 = 0;
T4CONbits.TCS = 0;
T4CONbits.TGATE = 0;
unsigned long increments = clock_frequency / frequency;
if(increments < 65535){
PR4 = increments;
T4CONbits.TCKPS = 0b00;
} else if (increments < 8 * 65535) {
PR4 = increments / 8;
T4CONbits.TCKPS = 0b01;
}
}
//Mark it as volatile as it will heavily be modified by interrupt !!!
volatile sbcp_t sbcp;
void init_dma_for_uart(){
DMA0CONbits.SIZE = 0; // use word
DMA0CONbits.DIR = 0; // from peripherical to RAM
DMA0CONbits.HALF = 0; // inetrrupt after all receive
DMA0CONbits.NULLW = 0; // non null write
DMA0CONbits.AMODE = 0; //indirect register with post increment
DMA0CONbits.MODE = 1; //One shot, no ping pong
DMA0CNT = 0; //1 transfers
DMA0PAD = (volatile unsigned int) &U1RXREG;
DMA0STA = buffer_fifo_get_dma_addr(sbcp.rx_fifo.tail);
DMA0REQ = 0x000b; //UART 1 RX irq
IFS0bits.DMA0IF = 0; //clear DMA interrupt flag
IEC0bits.DMA0IE = 1; //enable dmao interrupt
//DISABLE CHANNEL
DMA0CONbits.CHEN = 0; //disable channel 0
//activate DMA 0
//initialize DMA 1 for BUS TX
DMA1CONbits.SIZE = 0; //use word
DMA1CONbits.DIR = 1; //from RAM to periph
DMA1CONbits.HALF = 0; //interrupt after all received char
DMA1CONbits.NULLW = 0; //normal mode
DMA1CONbits.AMODE = 0; //indirect register with post increment
DMA1CONbits.MODE = 1; //One shot, no ping pong
DMA1CNT = 0; //1 byte, should be overrwriten when launching
DMA1PAD = (volatile unsigned int) &U1TXREG;
DMA1STA = __builtin_dmaoffset(bus_tx_buffer); //should be overwritten when launching
DMA1REQ = 0x000c; //UART 1 TX IRQ
IFS0bits.DMA1IF = 0; //clear DMA interrupt flag
IEC0bits.DMA1IE = 1; //ENABLE DMA1 INTERRUPT !!!!
//DISABLE CHANNEL, WILL BE CONFIGURED AND ENABLED WHEN NEEDED
DMA1CONbits.CHEN = 0;
}
void sbcp_init_settings(sbcp_settings * s ,
sbcp_class klass, sbcp_id id,
sbcp_fw_version version,
gpio send_enable,
gpio receive_disable) {
s->baudrate = 3310000UL;
s->clock_frequency = 39613750UL;
s->uart = UART_1;
s->timeout_frequency = 2000;
s->klass = klass;
s->id = id;
s->version = version;
s->init_instruction = 0;
s->init_register = 0;
s->send_enable = send_enable;
s->receive_disable = receive_disable;
}
void sbcp_init(sbcp_settings * s){
//set first all intern variables
buffer_fifo_set_empty(sbcp.rx_fifo);
buffer_fifo_set_empty(sbcp.rx_ready_fifo);
sbcp.rx_state = SBCP_RX_WAIT_FOR_HEADER_BYTE;
sbcp.rx_error = 0;
sbcp.current_rx_packet = 0;
sbcp.bus_uart_error_flags = SBCP_UART_ALL_ERRORS_DISABLED;
sbcp.send_enable = s->send_enable;
sbcp.receive_disable = s->receive_disable;
sbcp_clear_low_latency_register_definitions();
sbcp_init_registers(s->klass,s->id,s->version,s->init_register);
//prepare the buffers
//sbcp.ll_payload_size = s->ll_feedback_size;
sbcp.tx_state = SBCP_TX_BUFFER_FREE;
sbcp.ll_flags = SBCP_LL_NOTHING;
sbcp.is_for_us = 0;
sbcp.ll_previous_error_code = SBCP_NO_ERROR;
init_buffers(SBCP_MY_CLASS,SBCP_MY_ID);
sbcp_init_instructions(s->init_instruction);
init_uart_for_sbcp(s->uart,s->baudrate,s->clock_frequency);
init_dma_for_uart();
init_timer_4(s->clock_frequency,s->timeout_frequency);
//now start everything
uart_line_in_rx();
//now start the actual rx
dma_init_bus_rx();
}
/*************************************************************************
*
* FUNCTIONS
*
*************************************************************************/
void sbcp_low_latency_process(){
uint8_t i;
sbcp_error error = SBCP_NO_ERROR;
sbcp_reg_val val;
sbcp_reg_address reg;
if(sbcp.ll_flags & SBCP_LL_RX_IS_AVAILABLE){
sbcp.ll_flags |= SBCP_LL_IS_DISABLED;
sbcp.ll_previous_error_code = SBCP_NO_ERROR;
for(i = 0; i < sbcp.ll_in_size && error == SBCP_NO_ERROR; ++i){
reg = sbcp.ll_in_registers[i];
sbcp.ll_flags |= SBCP_LL_IS_DISABLED;
val.msb = sbcp.ll_payload[2 * i];
val.lsb = sbcp.ll_payload[2 * i + 1];
sbcp.ll_flags &= ~(SBCP_LL_RX_IS_AVAILABLE | SBCP_LL_IS_DISABLED);
IEC0bits.DMA0IE = 1; //will rethrow interrupt
if(sbcp_reg_clbck(reg) != SBCP_REG_NO_CALLBACK){
error = (*(sbcp_reg_clbck(reg)))(reg,&val);
if(error != SBCP_NO_ERROR){
sbcp.ll_previous_error_code = error;
} else {
sbcp_reg_table[reg] = val;
}
}
}
sbcp.ll_flags &= ~(SBCP_LL_RX_IS_AVAILABLE | SBCP_LL_IS_DISABLED);
IEC0bits.DMA0IE = 1; //will rethrow interrupt
}
if(sbcp.ll_flags & SBCP_LL_TX_IS_AVAILABLE && DMA1CONbits.CHEN == 0){
sbcp.ll_flags |= SBCP_LL_IS_DISABLED;
bus_ll_tx_buffer[SBCP_PAYLOAD_SIZE_POS] = sbcp.ll_out_size * 2;
bus_ll_tx_buffer[SBCP_INSTRUCTION_POS] = sbcp.ll_previous_error_code;
uint8_t cs = SBCP_MY_CLASS + SBCP_MY_ID + sbcp.ll_previous_error_code + sbcp.ll_out_size * 2;
// to remove some warnings when LOW_LATENCY_OUT_SIZE == 0
#if SBCP_LOW_LATENCY_OUT_SIZE > 0
for(i = 0 ; i < sbcp.ll_out_size; ++i){
reg = sbcp.ll_out_registers[i];
bus_ll_tx_buffer[SBCP_PAYLOAD_START_POS + 2 * i] = sbcp_reg_msb(reg);
bus_ll_tx_buffer[SBCP_PAYLOAD_START_POS + 2 * i + 1] = sbcp_reg_lsb(reg);
cs += sbcp_reg_msb(reg);
cs += sbcp_reg_lsb(reg);
}
#endif
bus_ll_tx_buffer[SBCP_PAYLOAD_START_POS + sbcp.ll_out_size * 2] = cs;
sbcp.ll_flags &= ~(SBCP_LL_TX_IS_AVAILABLE | SBCP_LL_IS_DISABLED);
IEC0bits.DMA0IE = 1; //will rethrow interrupt
}
}
void sbcp_process()
{
sbcp_low_latency_process();
//first clear overrun errors that may occurs, even if it is really, really bad
if(U1STAbits.OERR == 1 ){
U1STAbits.OERR = 0;//we lose all buffer data, but it means that DMA was
// activated while data was inside ! this is bad. To avoid this really
// bad situation, DMA0Channel should always be on, most of the time
// (could be disabled while Tx over the bus however).
// if we fail to do so, the UART will fill up, and we may loose some
// bytes. This is really, really bad since :
// - If master send a new packet very quickly we may miss our packet if
// we don't listen quickly enough.
// - If we start the recption inside a packet. We may, with bad luck
// start to see the sequence 0xff xx xx XX with XX being a relatively
// large number. Then we are screwed and may loose even more packet,
// for a long long time.
}
// Handle timeout
//right now if their is any error, we don't read the packet at all. Thus the
//master will issue a timeout. It would be nice, in case of an Frame error
//to send back an error packet
if(sbcp.rx_error){
//disable UART and DMA
U1MODEbits.UARTEN = 0;
U1STAbits.UTXEN = 0;
DMA0CONbits.CHEN = 0;
DMA0STA = buffer_fifo_get_dma_addr(sbcp.rx_fifo.tail);
DMA0CNT = 0;
sbcp.rx_state = SBCP_RX_WAIT_FOR_HEADER_BYTE;
DMA0CONbits.CHEN = 1;
U1MODEbits.UARTEN = 1;
U1STAbits.UTXEN = 1 ;
sbcp.rx_error = 0;
}
if(!buffer_fifo_empty(sbcp.rx_ready_fifo)) {
unsigned int * packet = (unsigned int * ) (buffer_fifo_get_cpu_addr(sbcp.rx_ready_fifo.head));
if (packet[SBCP_ID_POS] == SBCP_MY_ID &&
packet[SBCP_CLASS_POS] == SBCP_MY_CLASS) {
unsigned char csPos =
(packet[SBCP_PAYLOAD_SIZE_POS] + SBCP_PAYLOAD_START_POS) & SBCP_PACKET_SIZE_MASK;
unsigned char correctCS = sbcp_compute_packet_cs(packet);
if(packet[csPos] == correctCS){
sbcp_instruction inst = packet[SBCP_INSTRUCTION_POS];
sbcp_process_instruction(inst,packet);
} else {
sbcp_send_error_r_packet(SBCP_CERR_CS_INCORRECT);
}
} else if(DMA0CONbits.CHEN == 0){
dma_init_bus_rx();
}
buffer_fifo_increment_head(sbcp.rx_ready_fifo);
buffer_fifo_increment_head(sbcp.rx_fifo);
}
if (sbcp.tx_state == SBCP_TX_BUFFER_OCCUPIED) { //FLAGS IN_TX not set !
dma_init_bus_tx(bus_tx_buffer);
sbcp.tx_state |= SBCP_TX_BUFFER_IN_TX;//mark it to not send it lot of time !!!!
}
if(sbcp.rx_state == SBCP_RX_OVERFLOW_PENDING ){
if(!buffer_fifo_full(sbcp.rx_fifo)){
dma_init_bus_rx();
}
}
// Transfering packets from BUS TX ring buffer to BUS UART output
// Only transmit data to UART buffer if UART buffer is empty
// and ring buffer is not empty. Do not waste time with busy waiting.
// for UART that is connected to RS485
// check if we wait for a new packet
}
void __attribute__((__interrupt__ , no_auto_psv)) _DMA0Interrupt(void) {
switch (sbcp.rx_state) {
case SBCP_RX_WAIT_FOR_HEADER_BYTE : {
if(*(sbcp.current_rx_packet) != 0X00ff){
//resets the reception
DMA0CONbits.CHEN = 0;
DMA0CONbits.CHEN = 1;
} else {
dma_bus_rx_end_header();
timer_4_start();
//DMA_BUS_ENABLE_ERR_DETECTION;
}
break;
}
case SBCP_RX_WAIT_FOR_FULL_HEADER :
{
dma_bus_rx_full_packet();
break;
}
case SBCP_RX_WAIT_FOR_FULL_PACKET : {
//first of all, we check if the packet is for us.
if(sbcp.current_rx_packet[SBCP_ID_POS] != SBCP_MY_ID ||
sbcp.current_rx_packet[SBCP_CLASS_POS] != SBCP_MY_CLASS){
//the packet is not for us, we re-init the DMA 0 channel, and we
//the current incoming packet.
DMA0STA -= 8; // we remove the 4 first pointed bytes
DMA0CNT = 0; //just one byte reception
DMA0CONbits.CHEN = 1; //channel is resetted
sbcp.rx_state = SBCP_RX_WAIT_FOR_HEADER_BYTE;
//we are waiting the first byte
} else { //the packet is for us
if(sbcp.ll_flags & SBCP_LL_IS_DISABLED){
IEC0bits.DMA0IE = 0;
//we DISABLE interupt, and DON'T CLEAR flag.
//When we clear the FLAG SBCP_IS_DISABLED, we are intended
//to re-enable interrupt, so We will come here again
return;
}
//We check if it is the special low latency instruction
//this instruction should be responded ASAP, so in this interruption.
if (sbcp.current_rx_packet[SBCP_INSTRUCTION_POS] == SBCP_INST_LOW_LATENCY_INSTRUCTION) {
unsigned int i;
//of course checksum should be checked
unsigned char correctChecksum = SBCP_MY_CLASS
+ SBCP_MY_ID
+ SBCP_INST_LOW_LATENCY_INSTRUCTION;
uint8_t payload_size = sbcp.current_rx_packet[SBCP_PAYLOAD_SIZE_POS];
correctChecksum += payload_size;
for(i = 0; i < payload_size; ++i){
correctChecksum += sbcp.current_rx_packet[SBCP_PAYLOAD_START_POS + i];
}
if(correctChecksum == sbcp.current_rx_packet[SBCP_PAYLOAD_START_POS + payload_size]) {
//we use the already prepared standard message
dma_init_bus_tx(bus_ll_tx_buffer);
//we save the incomming payload.
for(i = 0; i < payload_size; ++i){
sbcp.ll_payload[i] = sbcp.current_rx_packet[SBCP_PAYLOAD_START_POS + i];
}
//we specify that new data is available to treat it at the appropriate time
sbcp.ll_flags |= SBCP_LL_RX_IS_AVAILABLE;
} else { //we get a bad checksum
//we use the already prepared error
dma_init_bus_tx(bus_ll_tx_error_buffer);
}
sbcp.tx_state = SBCP_TX_BUFFER_IN_TX;
//we mark it as treated since it was for us, and we have treated it.
buffer_fifo_increment_head(sbcp.rx_ready_fifo);
}
//the packet is for us (just imagine that this line is just
// after the else :) ) so we mark it as ready for treatment. If
// it was a low latency one, it would already have been already
//treated.
buffer_fifo_increment_tail(sbcp.rx_ready_fifo);
}
//now we clear the timer 4. Interruption may have already occured,
//but masked by this one that have a higher priority. To not mess up
//the whole thing, we should clear the corresponding interrupt flag.
timer_4_stop_and_clear();
IFS1bits.T4IF = 0;
//if the interruption have occured for some magic reason, or
//obviously deficient programming skill of mine, it should remove
//any uterly bad effect
sbcp.rx_error &= ~(SBCP_CERR_HOST_TIMEOUT);
break;
}
default :
break;
}
//clear the interrupt flag !
IFS0bits.DMA0IF = 0;
}
//Transfer is completely in UART Queue
void __attribute__((__interrupt__ , no_auto_psv )) _DMA1Interrupt(void){
U1STAbits.UTXISEL0 = 1; //stop only when all operation are finished
//clear any pending flags (which is set BTW)
barrier();
IFS0bits.U1TXIF = 0;
//enable TX interrupt to detect when trnasfer is actually finished (not just in queue)
IEC0bits.U1TXIE = 1;
barrier();
//clear the interrupt flag !
IFS0bits.DMA1IF = 0;
}
void __attribute__((__interrupt__ , no_auto_psv)) _U1TXInterrupt(void) {
/*
while(!(U1STAbits.TRMT)){
Nop();
}
*/
uart_line_in_rx();
//here all Tx operations are done
//We start the reset of the UART. indeed , UART has been of for a time now
//so a OERR could have occur, this is safer for two instructions !
U1MODEbits.UARTEN = 0; //disabled.
U1STAbits.UTXEN = 0;
//we switch the port back to Rx mode
// TODO we may need to re-enable Tx ! check doc for this !
// TODO check the error handling code too !
dma_init_bus_rx();
//now reception is OK !
sbcp.tx_state = SBCP_TX_BUFFER_FREE;
IEC0bits.U1TXIE = 0; //disable interrut
U1STAbits.UTXISEL0 = 0; //Interrupt every char
IFS0bits.U1TXIF = 0; //clear interrupt !
U1MODEbits.UARTEN = 1; //resetted, buffer are now empty !
U1STAbits.UTXEN = 1;
};
/* Timer 3 is used to create the timeout for bus receiver part of the SBCP protocol */
void __attribute__((__interrupt__, no_auto_psv)) _T4Interrupt(void)
{
/* Interrupt Service Routine code goes here */
timer_4_stop_and_clear();
sbcp.rx_error |= SBCP_CERR_BUS_TIMEOUT;
IFS1bits.T4IF = 0; // Clear Timer 3 Interrupt Flag
}
void sbcp_mark_tx_buffer_occupied(){
sbcp.tx_state |= SBCP_TX_BUFFER_OCCUPIED;
}
int sbcp_append_low_latency_in_register(sbcp_reg_address address){
if(address >= SBCP_REG_TABLE_SIZE){
return 1;
}
if(! (sbcp_reg_flags(address) & SBCP_REG_WRITABLE) ){
return 1;
}
if( sbcp_reg_flags(address) & SBCP_REG_PERSISTENT){
return 1;
}
if(sbcp.ll_in_size >= SBCP_LOW_LATENCY_IN_SIZE){
return 1;
}
sbcp.ll_in_registers[sbcp.ll_in_size] = address;
++sbcp.ll_in_size;
return 0;
}
int sbcp_append_low_latency_out_register(sbcp_reg_address address){
if(address >= SBCP_REG_TABLE_SIZE){
return 1;
}
if(! (sbcp_reg_flags(address) & SBCP_REG_READABLE) ){
return 1;
}
if(sbcp.ll_out_size >= SBCP_LOW_LATENCY_OUT_SIZE){
return 1;
}
if(! (sbcp_reg_flags(address) & SBCP_REG_LL_READABLE ) ) {
return 1;
}
sbcp.ll_out_registers[sbcp.ll_out_size] = address;
sbcp.ll_out_size += 1;
return 0;
}
void sbcp_mark_new_low_latency_data_available(){
sbcp.ll_flags |= SBCP_LL_TX_IS_AVAILABLE;
}
void sbcp_clear_low_latency_register_definitions(){
unsigned int i;
for(i = 0; i < SBCP_LOW_LATENCY_IN_SIZE ; ++i){
sbcp.ll_payload[2 * i ] = 0;
sbcp.ll_payload[2 * i + 1 ] = 0;
sbcp.ll_in_registers[i] = 0;
}
for(i = 0; i< SBCP_LOW_LATENCY_OUT_SIZE; ++i){
sbcp.ll_out_registers[i] = 0;
}
sbcp.ll_in_size = 0;
sbcp.ll_out_size = 0;
}
sbcp_payload_size sbcp_get_ll_in_size(){
return sbcp.ll_in_size;
}
sbcp_payload_size sbcp_get_ll_out_size(){
return sbcp.ll_out_size;
}
sbcp_reg_address sbcp_get_ll_in_register(sbcp_payload_size index){
if(index >= sbcp.ll_in_size){
return 0xff;
}
return sbcp.ll_in_registers[index];
}
sbcp_reg_address sbcp_get_ll_out_register(sbcp_payload_size index){
if(index >= sbcp.ll_out_size){
return 0xff;
}
return sbcp.ll_out_registers[index];
}
#else
int sbcp_append_low_latency_in_register(sbcp_reg_address address){
return 1;
}
int sbcp_append_low_latency_out_register(sbcp_reg_address address){
return 1;
}
void sbcp_clear_low_latency_register_definitions(){
}
sbcp_payload_size sbcp_get_ll_in_size(){
return 0;
}
sbcp_payload_size sbcp_get_ll_out_size(){
return 0;
}
sbcp_reg_address sbcp_get_ll_in_register(sbcp_payload_size index){
return 0xff;
}
sbcp_reg_address sbcp_get_ll_out_register(sbcp_payload_size index){
return 0xff;
}
#endif //SBCP_PROVIDE_MAIN

Event Timeline