Skip to content

Commit

Permalink
Merge pull request #632 from rogerclarkmelbourne/Phonog-Phonog-patch-1
Browse files Browse the repository at this point in the history
Added CAN functionality
  • Loading branch information
stevstrong committed Nov 3, 2023
2 parents 33f4ee2 + 380b863 commit 317348a
Show file tree
Hide file tree
Showing 14 changed files with 2,570 additions and 0 deletions.
1 change: 1 addition & 0 deletions STM32F1/cores/maple/libmaple/rcc_f1.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const struct rcc_dev_info rcc_dev_table[] = {
[RCC_TIMER13] = { .clk_domain = APB1, .line_num = 7 },
[RCC_TIMER14] = { .clk_domain = APB1, .line_num = 8 },
#endif
[RCC_CAN] = { .clk_domain = APB1, .line_num = 25 }, //! JMD after X893
};

__deprecated
Expand Down
15 changes: 15 additions & 0 deletions STM32F1/cores/maple/libmaple/usb/stm32f1/usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,25 @@ static void usb_resume(RESUME_STATE eResumeSetVal) {
}
}

// JMD : default ISRs of CAN, to be overridden if HardwareCAN library is used in sketch
void __attribute__((weak)) USB_HP_CAN_TX_IRQHandler(void)
{ ; } // Dummy ISR

void __irq_usb_hp_can_tx(void)
{
USB_HP_CAN_TX_IRQHandler () ;
}

uint8 __attribute__((weak)) CAN_RX0_IRQ_Handler(void)
{ return 0 ; } // Dummy ISR

#define SUSPEND_ENABLED 1
__weak void __irq_usb_lp_can_rx0(void) {
uint16 istr = USB_BASE->ISTR;

if (CAN_RX0_IRQ_Handler()) //! JMD : Call to CAN ISR, returns 1 CAN is active
return; //! JMD

/* Use USB_ISR_MSK to only include code for bits we care about. */

#if (USB_ISR_MSK & USB_ISTR_RESET)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#ifdef CHANGES_INCLUDE

****** DETAILS OF THE CHANGES TO BE DONE TO THE CORE TO BE ABLE TO USE THE LIBRARY HardwareCAN ******

1) History
The Hardware CAN library was originally published in the Maple Leaflabs forum by X893.
I tested it, and found bugs, which I fixed in the code. My fixes are commented with the initials JMD.
The most important things that missed was to connect the interrupt service routine to the CAN interrupt vector.
The problem is that in the F1 family, this vector is shared with the USB vector, as is some of the memory. Thus,
when one wants to use the CAN, the USB becomes unavailable. This is a severe drawback of this chip, but one has to cope with this.

2) Changes performed

2.1) In file C:\ArduinoForSTM32\arduino-1.6.9\hardware\Arduino_STM32-master\STM32F1\cores\maple\libmaple\rcc_f1.c
inserted 1 line, position 96:
[RCC_CAN] = { .clk_domain = APB1, .line_num = 25 }, //! JMD after X893

2.2) In file C:\ArduinoForSTM32\arduino-1.6.9\hardware\Arduino_STM32-master\STM32F1\system\libmaple\stm32f1\include\series\rcc.h
inserted 1 line, position 442:
RCC_CAN, //! JMD after X893

2.3) In file C:\ArduinoForSTM32\arduino-1.6.9\hardware\Arduino_STM32-master\STM32F1\cores\maple\libmaple\usb\stm32f1\usb.c
2.3.1) inserted 12 lines, position 186
// JMD : default ISRs of CAN, to be overridden if HardwareCAN library is used in sketch
void __attribute__((weak)) USB_HP_CAN_TX_IRQHandler(void)
{ ; } // Dummy ISR

void __irq_usb_hp_can_tx(void)
{
USB_HP_CAN_TX_IRQHandler () ;
}

uint8 __attribute__((weak)) CAN_RX0_IRQ_Handler(void)
{ return 1 ; } // Dummy ISR

2.3.2) and altered function void __irq_usb_lp_can_rx0(void)
Was

void __irq_usb_lp_can_rx0(void) {
uint16 istr = USB_BASE->ISTR;

/* Use USB_ISR_MSK to only include code for bits we care about. */

Becomes

void __irq_usb_lp_can_rx0(void) {
uint16 istr = USB_BASE->ISTR;

if (CAN_RX0_IRQ_Handler()) //! JMD : Call to CAN ISR, returns 1 CAN is active
return; //! JMD

/* Use USB_ISR_MSK to only include code for bits we care about. */
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#include <HardwareCAN.h>
#include "changes.h"
/*
* Example of use of the HardwareCAN library
* This application receives two frames containing various data. It also produces data that are sent periodically using another two frames.
* Please read the file changes.h to see the changes to be performed to the core in order to use this
*/
// Define the values of the identifiers
#define GYRO_ID 0x27
#define JOYSTICK_VALUES_ID 0x5A
#define TANK_LEVEL_ID 0x78
#define MOTOR_CONTROL_ID 0x92

// Limit time to flag a CAN error
#define CAN_TIMEOUT 100
#define CAN_DELAY 10 // ms between two processings of incoming messages
#define CAN_SEND_RATE 200 // ms between two successive sendings

// Message structures. Each message has its own identifier. As many such variables should be defined
// as the number of different CAN frames the application has to send. Here, they are two.
CanMsg msgGyroscope ;
CanMsg msgMotorControl ;

// Traffic handling data
int CANquietTime ; // Quiet time counter to detect no activity on CAN bus
bool CANError ; // Indicates that incoming CAN traffic is missing
int CANsendDivider ; // Used to send frames once every so many times loop() is called

// Applicaton variables
int Contents[4] ; // Contents of the four tanks
int JoystickX ; // Setting of the joystick, X axis
int JoystickY ; // ... Y axis
int AngularRate ; // Output of local gyroscope
int Throttle ; // Motor control value, produced by some local processing
bool ErreurGyroscope = false ;

// Instanciation of CAN interface
HardwareCAN canBus(CAN1_BASE);


// Note : for the predefined identifiers, please have a look in file can.h

void CANSetup(void)
{
CAN_STATUS Stat ;

// Initialize the message structures
// A CAN structure includes the following fields:
msgGyroscope.IDE = CAN_ID_STD; // Indicates a standard identifier ; CAN_ID_EXT would mean this frame uses an extended identifier
msgGyroscope.RTR = CAN_RTR_DATA; // Indicated this is a data frame, as opposed to a remote frame (would then be CAN_RTR_REMOTE)
msgGyroscope.ID = GYRO_ID ; // Identifier of the frame : 0-2047 (0-0x3ff) for standard idenfiers; 0-0x1fffffff for extended identifiers
msgGyroscope.DLC = 3; // Number of data bytes to follow
msgGyroscope.Data[0] = 0x0; // Data bytes, there can be 0 to 8 bytes.
msgGyroscope.Data[1] = 0x0;
msgGyroscope.Data[2] = 0x0;

msgMotorControl.IDE = CAN_ID_STD;
msgMotorControl.RTR = CAN_RTR_DATA;
msgMotorControl.ID = MOTOR_CONTROL_ID ;
msgMotorControl.DLC = 2;
msgMotorControl.Data[0] = 0x0;
msgMotorControl.Data[1] = 0x0;

// Initialize CAN module
canBus.map(CAN_GPIO_PB8_PB9); // This setting is already wired in the Olimexino-STM32 board
Stat = canBus.begin(CAN_SPEED_125, CAN_MODE_NORMAL); // Other speeds go from 125 kbps to 1000 kbps. CAN allows even more choices.

canBus.filter(0, 0, 0);
canBus.set_irq_mode(); // Use irq mode (recommended), so the handling of incoming messages
// will be performed at ease in a task or in the loop. The software fifo is 16 cells long,
// allowing at least 15 ms before processing the fifo is needed at 125 kbps
Stat = canBus.status();
if (Stat != CAN_OK)
/* Your own error processing here */ ; // Initialization failed
}

// Send one frame. Parameter is a pointer to a frame structure (above), that has previously been updated with data.
// If no mailbox is available, wait until one becomes empty. There are 3 mailboxes.
CAN_TX_MBX CANsend(CanMsg *pmsg)
{
CAN_TX_MBX mbx;

do
{
mbx = canBus.send(pmsg) ;
#ifdef USE_MULTITASK
vTaskDelay( 1 ) ; // Infinite loops are not multitasking-friendly
#endif
}
while(mbx == CAN_TX_NO_MBX) ; // Waiting outbound frames will eventually be sent, unless there is a CAN bus failure.
return mbx ;
}

// Process incoming messages
// Note : frames are not fully checked for correctness: DLC value is not checked, neither are the IDE and RTR fields. However, the data is guaranteed to be corrrect.
void ProcessMessages(void)
{
int Pr = 0 ;
int i ;

CanMsg *r_msg;

// Loop for every message in the fifo
while ((r_msg = canBus.recv()) != NULL)
{
CANquietTime = 0 ; // Reset at each received frame
CANError = false ; // Clear CAN silence error
switch ( r_msg->ID )
{
case TANK_LEVEL_ID : // This frame contains four 16-bit words, little endian coded
for ( i = 0 ; i < 4 ; i++ )
Contents[i] = (int)r_msg->Data[2*i] | ((int)r_msg->Data[(2*i)+1]) << 8 ;
break ;

case JOYSTICK_VALUES_ID : // This frame contains two 16-bit words, little endian coded
Pr = (int)r_msg->Data[0] ;
Pr |= (int)r_msg->Data[1] << 8 ;
JoystickX = Pr ;

Pr = (int)r_msg->Data[2] ;
Pr |= (int)r_msg->Data[3] << 8 ;
JoystickY = Pr ;
break ;

default : // Any frame with a different identifier is ignored
break ;
}

canBus.free(); // Remove processed message from buffer, whatever the identifier
#ifdef USE_MULTITASK
vTaskDelay( 1 ) ; // Infinite loops are not multitasking-friendly
#endif
}
}

// Send messages
// Prepare and send 2 frames containing the value of process variables
// Sending all frames at once is a choice; they could be sent separately, at different times and rates.
void SendCANmessages(void)
{
// Prepare Gyroscope frame : send angular rate
msgGyroscope.Data[0] = AngularRate & 0xff ;
msgGyroscope.Data[1] = ( AngularRate >> 8 ) & 0xff ;
msgGyroscope.Data[2] = ErreurGyroscope ? 1 : 0 ;
CANsend(&msgGyroscope) ; // Send this frame

msgMotorControl.Data[0] = Throttle & 0xff ;
msgMotorControl.Data[1] = ( Throttle >> 8 ) & 0xff ;
CANsend(&msgMotorControl) ;
}

// The application program starts here
void setup() {
// put your setup code here, to run once:
CANSetup() ; // Initialize the CAN module and prepare the message structures.
}

void loop() {
// Process incoming messages periodically (should be often enough to avoid overflowing the fifo)
ProcessMessages() ; // Process all incoming messages, update local variables accordingly

// This is an example of timeout management. Here it is global to all received frames;
// it could be on a frame by frame basis, with as many control variables as the number of frames.
CANquietTime++ ;
if ( CANquietTime > CAN_TIMEOUT )
{
CANquietTime = CAN_TIMEOUT + 1 ; // To prevent overflowing this variable if silence prolongs...
CANError = true ; // Flag CAN silence error. Will be cleared at first frame received
}

// Send messages containing variables to publish. Sent less frequently than the processing of incoming frames (here, every 200 ms)
CANsendDivider-- ;
if ( CANsendDivider < 0 )
{
CANsendDivider = CAN_SEND_RATE / CAN_DELAY ;
SendCANmessages() ;
}
delay(CAN_DELAY) ; // The delay must not be greater than the time to overflow the incoming fifo (here about 15 ms)
}


40 changes: 40 additions & 0 deletions STM32F1/libraries/HardwareCAN/keywords.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#######################################
# Syntax Coloring Map For HardwareCAN
#######################################

#######################################
# Datatypes (KEYWORD1)
#######################################
HardwareCAN KEYWORD1
CanMsg KEYWORD1
CAN_TX_MBX KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################
release KEYWORD2 CAN_Release
send KEYWORD2 CAN_Send
read KEYWORD2 CAN_Read
recv KEYWORD2 CAN_Recieve
set_pool_mode KEYWORD2 CAN_Set_Pool_Mode
set_irq_mode KEYWORD2 CAN_Set_IRQ_Mode
fifo_ready KEYWORD2 CAN_Fifo_Ready
filter KEYWORD2 CAN_Filter
free KEYWORD2 CAN_Free
available KEYWORD2 CAN_Available

#######################################
# Constants (LITERAL1)
#######################################
CAN_GPIO_PD0_PD1 LITERAL1
CAN_GPIO_PB8_PB9 LITERAL1
CAN_FIFO0 LITERAL1
CAN_FIFO1 LITERAL1
CAN_SPEED_125 LITERAL1
CAN_SPEED_250 LITERAL1
CAN_SPEED_500 LITERAL1
CAN_SPEED_1000 LITERAL1
CAN1_BASE LITERAL1
CAN2_BASE LITERAL1
CAN_ID_STD LITERAL1
CAN_RTR_DATA LITERAL1
9 changes: 9 additions & 0 deletions STM32F1/libraries/HardwareCAN/library.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name=HardwareCAN
version=1.0.0
author=Maple Leaflabs fixed by JMD
maintainer=JMD
sentence=Enables managing CAN communication using the built-in CAN port of the OLIMEX STM32 board.
paragraph=With this library you can use the built-in CAN port of the OLIMEX STM32. The library handles both standard and extended frames.
category=Communication
url=
architectures=*

0 comments on commit 317348a

Please sign in to comment.