Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
humanoides
tools
stm32_libraries
Commits
2535d0ee
Commit
2535d0ee
authored
Sep 07, 2021
by
smartinezs
Browse files
Added can implementation for f4 family
parent
74d7770a
Changes
3
Hide whitespace changes
Inline
Side-by-side
f4/can/include/can.h
0 → 100644
View file @
2535d0ee
#ifndef CAN_F4_H
#define CAN_F4_H
#include
"stm32f4xx_hal.h"
#include
"comm.h"
typedef
struct
{
unsigned
char
irq_priority
;
unsigned
char
irq_subpriority
;
}
TCAN_IRQ_Priorities
;
typedef
enum
{
CAN_10KBPS
,
CAN_20KBPS
,
CAN_50KBPS
,
CAN_83K3BPS
,
CAN_100KBPS
,
CAN_125KBPS
,
CAN_250KBPS
,
CAN_500KBPS
,
CAN_1000KBPS
}
CAN_SPEED
;
typedef
enum
{
VERSION_1
,
VERSION_2
,
VERSION_NULL
}
DynamixelVersion
;
typedef
struct
TDynamixelMessage
{
unsigned
char
received_bytes
;
/**
* \brief
*
*/
unsigned
char
rx_buffer
[
MAX_DYN_SLAVE_RX_BUFFER_LEN
];
/**
* \brief
*
*/
unsigned
char
Len_L
;
/**
* \brief
*
*/
unsigned
char
Len_H
;
/**
* \brief
*
*/
unsigned
int
length
;
/**
* \brief
*
*/
DynamixelVersion
version
;
/**
* \brief
*
*/
unsigned
char
packet_init
;
/**
* \brief
*
*/
unsigned
char
packet_incomplete
;
/**
* \brief
*
*/
unsigned
char
send_packet
;
/**
* \brief
*
*/
unsigned
char
iterations
;
/**
* \brief
*
*/
}
TDynamixelMessage
;
/* public functions */
void
can_init
(
TComm
*
comm_dev
,
CAN_InitTypeDef
*
conf
,
TCAN_IRQ_Priorities
*
priorities
);
void
can_config
(
TComm
*
comm_dev
,
CAN_InitTypeDef
*
conf
);
void
can_set_priorities
(
TComm
*
comm_dev
,
TCAN_IRQ_Priorities
*
priorities
);
void
can_set_bitrate
(
TComm
*
comm_dev
,
CAN_SPEED
bitrate
);
void
can_set_filter
(
TComm
*
comm_dev
,
CAN_FilterTypeDef
*
filter
);
void
can_set_can_id
(
TComm
*
comm_dev
,
unsigned
int
id
);
/* IRQ functions */
unsigned
char
can_send_irq
(
unsigned
char
first_byte
);
unsigned
char
can_enable_tx_irq
(
void
);
unsigned
char
can_receive_irq
(
void
);
unsigned
char
can_cancel_receive_irq
(
void
);
/* DMA functions */
unsigned
char
can_send_dma
(
unsigned
char
*
data
,
unsigned
short
int
length
);
unsigned
char
can_receive_dma
(
unsigned
char
*
data
,
unsigned
short
int
length
);
unsigned
char
can_cancel_receive_dma
(
void
);
#endif
f4/can/include/can_common.h
0 → 100644
View file @
2535d0ee
#ifndef CAN_COMMON_F1_H
#define CAN_COMMON_F1_H
typedef
struct
{
unsigned
char
irq_priority
;
unsigned
char
irq_subpriority
;
}
TCAN_IRQ_Priorities
;
enum
CAN_SPEED
{
//only applies when APB1 = 36Mhz
CAN_10KBPS
,
CAN_20KBPS
,
CAN_50KBPS
,
CAN_83K3BPS
,
CAN_100KBPS
,
CAN_125KBPS
,
CAN_250KBPS
,
CAN_500KBPS
,
CAN_1000KBPS
};
#endif
f4/can/src/can.c
0 → 100644
View file @
2535d0ee
#include
"can.h"
#define CAN CAN1
#define CAN_ENABLE_CLK __HAL_RCC_CAN1_CLK_ENABLE()
#define CAN_IRQnRX CAN1_RX0_IRQn
#define CAN_IRQnTX CAN1_TX_IRQn
#define CAN_TX_PIN GPIO_PIN_9
#define CAN_TX_GPIO_PORT GPIOB
#define CAN_ENABLE_TX_GPIO_CLK __HAL_RCC_GPIOB_CLK_ENABLE()
#define CAN_RX_PIN GPIO_PIN_8
#define CAN_RX_GPIO_PORT GPIOB
#define CAN_ENABLE_RX_GPIO_CLK __HAL_RCC_GPIOB_CLK_ENABLE()
// private variables
CAN_HandleTypeDef
CANHandle
;
CAN_FilterTypeDef
CANFilterConfig
;
CAN_TxHeaderTypeDef
CAN_txHeader
;
uint8_t
*
CAN_txMessage
;
uint32_t
TxMailbox
;
CAN_RxHeaderTypeDef
CAN_rxHeader
;
uint8_t
CAN_rxMessage
[
8
];
TComm
*
can_comm_dev
;
unsigned
char
can_dma_phase
;
unsigned
char
*
can_dma_phase_data
;
unsigned
short
int
can_dma_phase_length
;
unsigned
short
int
can_dma_phase_write_ptr
;
unsigned
char
can_missing_bytes_data
[
255
];
unsigned
short
int
can_missing_bytes_length
;
unsigned
short
int
can_missing_bytes_write_ptr
;
unsigned
short
int
can_send_lenght
;
unsigned
short
int
can_send_iterations
;
unsigned
char
can_send_data
[
255
];
TDynamixelMessage
message
;
void
can_init
(
TComm
*
comm_dev
,
CAN_InitTypeDef
*
conf
,
TCAN_IRQ_Priorities
*
priorities
)
{
CANHandle
.
Instance
=
CAN
;
can_config
(
comm_dev
,
conf
);
GPIO_InitTypeDef
GPIO_InitStructure
;
/* Enable GPIO clock */
CAN_ENABLE_TX_GPIO_CLK
;
CAN_ENABLE_RX_GPIO_CLK
;
CAN_ENABLE_CLK
;
/**CAN GPIO Configuration
PB8 ------> CAN_RX
PB9 ------> CAN_TX
*/
/* Configure CAN Tx and Rx as alternate function push-pull */
GPIO_InitStructure
.
Pin
=
CAN_TX_PIN
|
CAN_RX_PIN
;
GPIO_InitStructure
.
Mode
=
GPIO_MODE_AF_PP
;
GPIO_InitStructure
.
Pull
=
GPIO_NOPULL
;
GPIO_InitStructure
.
Speed
=
GPIO_SPEED_FREQ_VERY_HIGH
;
GPIO_InitStructure
.
Alternate
=
GPIO_AF9_CAN1
;
HAL_GPIO_Init
(
CAN_TX_GPIO_PORT
,
&
GPIO_InitStructure
);
can_set_priorities
(
comm_dev
,
priorities
);
HAL_CAN_Init
(
&
CANHandle
);
/* Initialize the comm structure */
comm_dev
->
send_irq
=
can_send_irq
;
comm_dev
->
enable_tx_irq
=
can_enable_tx_irq
;
comm_dev
->
receive_irq
=
can_receive_irq
;
comm_dev
->
cancel_receive_irq
=
can_cancel_receive_irq
;
if
(
comm_dev
->
use_dma
)
{
comm_dev
->
send_dma
=
can_send_dma
;
comm_dev
->
receive_dma
=
can_receive_dma
;
comm_dev
->
cancel_receive_dma
=
can_cancel_receive_dma
;
}
comm_dev
->
irq_send_cb
=
0x00000000
;
comm_dev
->
irq_receive_cb
=
0x00000000
;
comm_dev
->
dma_send_cb
=
0x00000000
;
comm_dev
->
dma_receive_cb
=
0x00000000
;
// /* initialize internal variables */
can_comm_dev
=
comm_dev
;
can_dma_phase
=
0x00
;
can_dma_phase_data
=
0x00
;
can_dma_phase_length
=
0
;
can_dma_phase_write_ptr
=
0
;
can_missing_bytes_length
=
0
;
can_missing_bytes_write_ptr
=
0
;
can_send_lenght
=
0
;
can_send_iterations
=
0
;
message
.
packet_init
=
0x00
;
message
.
packet_incomplete
=
0x00
;
message
.
send_packet
=
0x00
;
message
.
iterations
=
0
;
///////////////////////////////////////////////////////////////////////////
//default tx message Configuration
CAN_txHeader
.
DLC
=
1
;
CAN_txHeader
.
IDE
=
CAN_ID_STD
;
CAN_txHeader
.
RTR
=
CAN_RTR_DATA
;
CAN_txHeader
.
StdId
=
0x00
;
CAN_txHeader
.
ExtId
=
0x01
;
//default configration for the filer -> none
CANFilterConfig
.
FilterBank
=
0
;
CANFilterConfig
.
FilterMode
=
CAN_FILTERMODE_IDMASK
;
CANFilterConfig
.
FilterScale
=
CAN_FILTERSCALE_32BIT
;
CANFilterConfig
.
FilterIdHigh
=
0x0000
;
CANFilterConfig
.
FilterIdLow
=
0x0000
;
CANFilterConfig
.
FilterMaskIdHigh
=
0x0000
;
CANFilterConfig
.
FilterMaskIdLow
=
0x0000
;
CANFilterConfig
.
FilterFIFOAssignment
=
0
;
CANFilterConfig
.
FilterActivation
=
ENABLE
;
CANFilterConfig
.
SlaveStartFilterBank
=
0
;
//configure the filter
HAL_CAN_ConfigFilter
(
&
CANHandle
,
&
CANFilterConfig
);
//start the can device
HAL_CAN_Start
(
&
CANHandle
);
//Activate default interrupt when message in FIFO0
HAL_CAN_ActivateNotification
(
&
CANHandle
,
CAN_IT_RX_FIFO0_MSG_PENDING
);
}
void
can_config
(
TComm
*
comm_dev
,
CAN_InitTypeDef
*
conf
)
{
CANHandle
.
Init
.
Prescaler
=
conf
->
Prescaler
;
CANHandle
.
Init
.
Mode
=
conf
->
Mode
;
CANHandle
.
Init
.
SyncJumpWidth
=
conf
->
SyncJumpWidth
;
CANHandle
.
Init
.
TimeSeg1
=
conf
->
TimeSeg1
;
CANHandle
.
Init
.
TimeSeg2
=
conf
->
TimeSeg2
;
CANHandle
.
Init
.
TimeTriggeredMode
=
conf
->
TimeTriggeredMode
;
CANHandle
.
Init
.
AutoBusOff
=
conf
->
AutoBusOff
;
CANHandle
.
Init
.
AutoWakeUp
=
conf
->
AutoWakeUp
;
CANHandle
.
Init
.
AutoRetransmission
=
conf
->
AutoRetransmission
;
CANHandle
.
Init
.
ReceiveFifoLocked
=
conf
->
ReceiveFifoLocked
;
CANHandle
.
Init
.
TransmitFifoPriority
=
conf
->
TransmitFifoPriority
;
}
void
can_set_priorities
(
TComm
*
comm_dev
,
TCAN_IRQ_Priorities
*
priorities
)
{
HAL_NVIC_SetPriority
(
CAN_IRQnRX
,
priorities
->
irq_priority
,
priorities
->
irq_subpriority
);
HAL_NVIC_EnableIRQ
(
CAN_IRQnRX
);
HAL_NVIC_SetPriority
(
CAN_IRQnTX
,
priorities
->
irq_priority
,
priorities
->
irq_subpriority
);
HAL_NVIC_EnableIRQ
(
CAN_IRQnTX
);
}
// This only works on APB1 clock of 42Mhz
void
can_set_bitrate
(
TComm
*
comm_dev
,
CAN_SPEED
bitrate
)
{
switch
(
bitrate
)
{
case
CAN_10KBPS
:
CANHandle
.
Init
.
Prescaler
=
280
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_12TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_20KBPS
:
CANHandle
.
Init
.
Prescaler
=
140
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_12TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_50KBPS
:
CANHandle
.
Init
.
Prescaler
=
56
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_12TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_83K3BPS
:
CANHandle
.
Init
.
Prescaler
=
28
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_15TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_100KBPS
:
CANHandle
.
Init
.
Prescaler
=
28
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_12TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_125KBPS
:
CANHandle
.
Init
.
Prescaler
=
21
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_13TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_250KBPS
:
CANHandle
.
Init
.
Prescaler
=
12
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_11TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_500KBPS
:
CANHandle
.
Init
.
Prescaler
=
6
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_11TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
case
CAN_1000KBPS
:
CANHandle
.
Init
.
Prescaler
=
3
;
CANHandle
.
Init
.
TimeSeg1
=
CAN_BS1_11TQ
;
CANHandle
.
Init
.
TimeSeg2
=
CAN_BS2_2TQ
;
CANHandle
.
Init
.
SyncJumpWidth
=
CAN_SJW_1TQ
;
break
;
}
}
void
can_set_filter
(
TComm
*
comm_dev
,
CAN_FilterTypeDef
*
filter
)
{
HAL_CAN_ConfigFilter
(
&
CANHandle
,
filter
);
}
void
can_set_can_id
(
TComm
*
comm_dev
,
unsigned
int
id
)
{
CAN_txHeader
.
StdId
=
id
;
//CANHandle.pTxMsg->StdId = id;
}
/**
* HAL_CAN_ErrorCallback
* Callback function for CAN errors.
* @param CANHandle Pointer to a CAN_HandleTypeDef structure that contains
* the configuration information for a given CAN peripheral.
*/
void
HAL_CAN_ErrorCallback
(
CAN_HandleTypeDef
*
CANHandle
)
{
//HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4,GPIO_PIN_SET);
}
void
HAL_CAN_TxMailbox0CompleteCallback
(
CAN_HandleTypeDef
*
CANHandle
)
{
if
(
can_send_lenght
!=
0
)
{
if
(
can_send_lenght
>
8
)
{
CAN_txHeader
.
DLC
=
8
;
for
(
uint8_t
i
=
0
;
i
<
8
;
i
++
)
{
CAN_txMessage
[
i
]
=
can_send_data
[
i
+
(
8
*
can_send_iterations
)];
//CANHandle->pTxMsg->Data[i] = can_send_data[i+(8*can_send_iterations)];
}
can_send_lenght
=
can_send_lenght
-
8
;
can_send_iterations
++
;
//HAL_CAN_Transmit_IT(CANHandle);
HAL_CAN_AddTxMessage
(
CANHandle
,
&
CAN_txHeader
,
CAN_txMessage
,
&
TxMailbox
);
}
else
{
//CANHandle->pTxMsg->DLC = can_send_lenght;
for
(
uint8_t
i
=
0
;
i
<
can_send_lenght
;
i
++
)
{
CAN_txMessage
[
i
]
=
can_send_data
[
i
+
(
8
*
can_send_iterations
)];
//CANHandle->pTxMsg->Data[i] = can_send_data[i+(8*can_send_iterations)];
}
can_send_lenght
=
0
;
can_send_iterations
=
0
;
//HAL_CAN_Transmit_IT(CANHandle);
HAL_CAN_AddTxMessage
(
CANHandle
,
&
CAN_txHeader
,
CAN_txMessage
,
&
TxMailbox
);
uint8_t
byte
;
comm_do_dma_send
(
can_comm_dev
);
comm_do_irq_send
(
can_comm_dev
,
&
byte
);
}
}
}
void
HAL_CAN_RxFifo0MsgPendingCallback
(
CAN_HandleTypeDef
*
CANHandle
)
{
HAL_CAN_GetRxMessage
(
CANHandle
,
CAN_RX_FIFO0
,
&
CAN_rxHeader
,
CAN_rxMessage
);
if
(
can_missing_bytes_length
==
0
){
for
(
uint8_t
i
=
0
;
i
<
CAN_rxHeader
.
DLC
;
i
++
)
{
if
(
!
can_dma_phase
)
{
comm_do_irq_receive
(
can_comm_dev
,
CAN_rxMessage
[
i
]);
}
else
{
if
(
can_dma_phase_length
<
5
){
can_dma_phase_data
[
can_dma_phase_write_ptr
]
=
CAN_rxMessage
[
i
];
can_dma_phase_length
--
;
can_dma_phase_write_ptr
++
;
if
(
can_dma_phase_length
==
0
){
comm_do_dma_receive
(
can_comm_dev
);
}
}
}
}
if
(
can_dma_phase_length
>
4
){
for
(
uint8_t
i
=
0
;
i
<
CAN_rxHeader
.
DLC
;
i
++
)
can_missing_bytes_data
[
i
]
=
CAN_rxMessage
[
i
];
can_missing_bytes_length
=
can_dma_phase_length
-
4
;
can_missing_bytes_write_ptr
=
7
;
}
}
else
{
if
(
can_missing_bytes_length
<=
8
){
for
(
uint8_t
i
=
0
;
i
<
CAN_rxHeader
.
DLC
;
i
++
)
{
can_missing_bytes_write_ptr
++
;
can_missing_bytes_length
--
;
can_missing_bytes_data
[
can_missing_bytes_write_ptr
]
=
CAN_rxMessage
[
i
];
}
for
(
uint8_t
i
=
0
;
i
<=
can_missing_bytes_write_ptr
;
i
++
)
{
if
(
!
can_dma_phase
)
comm_do_irq_receive
(
can_comm_dev
,
can_missing_bytes_data
[
i
]);
else
{
can_dma_phase_data
[
can_dma_phase_write_ptr
]
=
can_missing_bytes_data
[
i
];
can_dma_phase_length
--
;
can_dma_phase_write_ptr
++
;
if
(
can_dma_phase_length
==
0
){
comm_do_dma_receive
(
can_comm_dev
);
can_missing_bytes_length
=
0
;
can_missing_bytes_write_ptr
=
0
;
}
}
}
}
else
{
for
(
uint8_t
i
=
0
;
i
<
CAN_rxHeader
.
DLC
;
i
++
)
{
can_missing_bytes_write_ptr
++
;
can_missing_bytes_length
--
;
can_missing_bytes_data
[
can_missing_bytes_write_ptr
]
=
CAN_rxMessage
[
i
];
}
}
}
}
// interrupt handlers
void
CAN1_RX0_IRQHandler
(
void
)
{
/* USER CODE BEGIN USB_LP_CAN1_RX0_IRQn 0 */
/* USER CODE END USB_LP_CAN1_RX0_IRQn 0 */
HAL_CAN_IRQHandler
(
&
CANHandle
);
/* USER CODE BEGIN USB_LP_CAN1_RX0_IRQn 1 */
/* USER CODE END USB_LP_CAN1_RX0_IRQn 1 */
}
void
CAN1_TX_IRQHandler
(
void
)
{
/* USER CODE BEGIN USB_HP_CAN1_TX_IRQn 0 */
/* USER CODE END USB_HP_CAN1_TX_IRQn 0 */
HAL_CAN_IRQHandler
(
&
CANHandle
);
/* USER CODE BEGIN USB_HP_CAN1_TX_IRQn 1 */
/* USER CODE END USB_HP_CAN1_TX_IRQn 1 */
}
unsigned
char
can_send_irq
(
unsigned
char
first_byte
)
{
return
0x00
;
}
unsigned
char
can_enable_tx_irq
(
void
)
{
return
0x00
;
}
unsigned
char
can_receive_irq
(
void
)
{
can_dma_phase
=
0x00
;
can_dma_phase_write_ptr
=
0x00
;
//HAL_CAN_Receive_IT(&CANHandle,CAN_FIFO0);
HAL_CAN_GetRxMessage
(
&
CANHandle
,
CAN_RX_FIFO0
,
&
CAN_rxHeader
,
CAN_rxMessage
);
return
0x00
;
}
unsigned
char
can_cancel_receive_irq
(
void
)
{
return
0x00
;
}
/* DMA functions */
unsigned
char
can_send_dma
(
unsigned
char
*
data
,
unsigned
short
int
length
)
{
__HAL_CAN_CLEAR_FLAG
(
&
CANHandle
,
CAN_FLAG_RQCP0
);
__HAL_CAN_CLEAR_FLAG
(
&
CANHandle
,
CAN_FLAG_TXOK0
);
if
(
length
>
8
){
CAN_txHeader
.
DLC
=
8
;
for
(
uint8_t
i
=
0
;
i
<
length
;
i
++
)
{
can_send_data
[
i
]
=
data
[
i
];
}
can_send_lenght
=
length
-
8
;
can_send_iterations
=
1
;
// for (uint8_t i=0;i<8;i++)
// {
// CANHandle.pTxMsg->Data[i] = data[i];
// }
//HAL_CAN_Transmit_IT(&CANHandle);
HAL_CAN_AddTxMessage
(
&
CANHandle
,
&
CAN_txHeader
,
data
,
&
TxMailbox
);
}
else
{
CAN_txHeader
.
DLC
=
length
;
// for (uint8_t i=0;i<length;i++)
// {
// CANHandle.pTxMsg->Data[i] = data[i];
// }
can_send_lenght
=
0
;
can_send_iterations
=
0
;
//HAL_CAN_Transmit_IT(&CANHandle);
HAL_CAN_AddTxMessage
(
&
CANHandle
,
&
CAN_txHeader
,
data
,
&
TxMailbox
);
uint8_t
byte
;
comm_do_dma_send
(
can_comm_dev
);
comm_do_irq_send
(
can_comm_dev
,
&
byte
);
}
return
0x00
;
}
unsigned
char
can_receive_dma
(
unsigned
char
*
data
,
unsigned
short
int
length
)
{
can_dma_phase
=
0x01
;
can_dma_phase_data
=
data
;
can_dma_phase_length
=
length
;
can_dma_phase_write_ptr
=
0
;
return
0x00
;
}
unsigned
char
can_cancel_receive_dma
(
void
)
{
can_dma_phase
=
0x00
;
can_dma_phase_data
=
0x00
;
can_dma_phase_length
=
0
;
can_dma_phase_write_ptr
=
0
;
//can_missing_bytes_length=0;
//can_missing_bytes_write_ptr=0;
return
0x00
;
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment